@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/script.js CHANGED
@@ -1,8 +1,27 @@
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
+ /**
4
+ * Maximum byte size allowed for a single pushed script element.
5
+ * BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
6
+ * the old 10,000-byte overall script-size cap.
7
+ */
3
8
  export const MAX_SCRIPT_BYTE_LENGTH = 520;
9
+ // Be friendly to bad ECMAScript parsers by not using bigint literals.
4
10
  // prettier-ignore
5
- export const OP = {
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);
14
+ // prettier-ignore
15
+ /**
16
+ * Bitcoin Script opcode table.
17
+ * @example
18
+ * Use opcode numbers when you need the raw byte form instead of Script mnemonics.
19
+ * ```ts
20
+ * import { OP } from '@scure/btc-signer/script.js';
21
+ * new Uint8Array([OP.OP_1, OP.OP_2, OP.CHECKMULTISIG]);
22
+ * ```
23
+ */
24
+ export const OP = /* @__PURE__ */ Object.freeze({
6
25
  OP_0: 0, PUSHDATA1: 76, PUSHDATA2: 77, PUSHDATA4: 78, '1NEGATE': 79,
7
26
  RESERVED: 80,
8
27
  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,
@@ -31,20 +50,43 @@ export const OP = {
31
50
  CHECKSIGADD: 186,
32
51
  // Invalid
33
52
  INVALID: 255,
34
- };
35
- export const OPNames = reverseObject(OP);
53
+ });
54
+ /**
55
+ * Reverse lookup map from opcode numbers back to names.
56
+ * @example
57
+ * Turn parsed opcode numbers back into their mnemonic names.
58
+ * ```ts
59
+ * import { OP, OPNames } from '@scure/btc-signer/script.js';
60
+ * OPNames[OP.CHECKSIG];
61
+ * ```
62
+ */
63
+ export const OPNames = /* @__PURE__ */ (() => Object.freeze(reverseObject(OP)))();
36
64
  // We can encode almost any number as ScriptNum, however, parsing will be a problem
37
65
  // since we can't know if buffer is a number or something else.
66
+ /**
67
+ * Coder for Bitcoin Script numbers.
68
+ * bytesLimit only constrains decode. encode still serializes any bigint in
69
+ * Script's signed-magnitude byte form so higher-level consumers can enforce
70
+ * opcode-specific 4-byte or 5-byte bounds separately.
71
+ * @param bytesLimit - maximum decoded length in bytes
72
+ * @param forceMinimal - whether to reject non-minimal encodings
73
+ * @returns Script number coder.
74
+ * @example
75
+ * Encode a small integer using Script number rules.
76
+ * ```ts
77
+ * ScriptNum().encode(1n);
78
+ * ```
79
+ */
38
80
  export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
39
81
  return P.wrap({
40
82
  encodeStream: (w, value) => {
41
- if (value === 0n)
83
+ if (value === _0n)
42
84
  return;
43
85
  const neg = value < 0;
44
86
  const val = BigInt(value);
45
87
  const nums = [];
46
- for (let abs = neg ? -val : val; abs; abs >>= 8n)
47
- nums.push(Number(abs & 0xffn));
88
+ for (let abs = neg ? -val : val; abs; abs >>= _8n)
89
+ nums.push(Number(abs & U8_MAX));
48
90
  if (nums[nums.length - 1] >= 0x80)
49
91
  nums.push(neg ? 0x80 : 0);
50
92
  else if (neg)
@@ -56,37 +98,52 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
56
98
  if (len > bytesLimit)
57
99
  throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
58
100
  if (len === 0)
59
- return 0n;
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);
60
105
  if (forceMinimal) {
61
- const data = r.bytes(len, true);
62
106
  // MSB is zero (without sign bit) -> not minimally encoded
63
- if ((data[data.length - 1] & 0x7f) === 0) {
107
+ if ((data[len - 1] & 0x7f) === 0) {
64
108
  // exception
65
- if (len <= 1 || (data[data.length - 2] & 0x80) === 0)
109
+ if (len <= 1 || (data[len - 2] & 0x80) === 0)
66
110
  throw new Error('Non-minimally encoded ScriptNum');
67
111
  }
68
112
  }
69
- let last = 0;
70
- let res = 0n;
71
- for (let i = 0; i < len; ++i) {
72
- last = r.byte();
73
- res |= BigInt(last) << (8n * BigInt(i));
74
- }
75
- if (last >= 0x80) {
76
- 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;
77
118
  res = -res;
78
119
  }
79
120
  return res;
80
121
  },
81
122
  });
82
123
  }
124
+ /**
125
+ * Attempts to decode a numeric script element into a JavaScript number.
126
+ * Accepts decoded small integers already represented as JS numbers and pushed
127
+ * `ScriptNum` byte payloads, but does not interpret opcode mnemonics like `1NEGATE`.
128
+ * @param op - script element to decode
129
+ * @param bytesLimit - maximum encoded length in bytes
130
+ * @param forceMinimal - whether to enforce minimal `ScriptNum` encoding
131
+ * @returns Decoded number, or `undefined` when the element is not a JS number or valid `ScriptNum` bytes.
132
+ * @example
133
+ * Decode a script element back into a JavaScript number when possible.
134
+ * ```ts
135
+ * OpToNum(1);
136
+ * ```
137
+ */
83
138
  export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
84
139
  if (typeof op === 'number')
85
140
  return op;
86
141
  if (isBytes(op)) {
87
142
  try {
88
143
  const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
89
- if (val > Number.MAX_SAFE_INTEGER)
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)
90
147
  return;
91
148
  return Number(val);
92
149
  }
@@ -96,6 +153,29 @@ export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
96
153
  }
97
154
  return;
98
155
  }
156
+ // Shared raw pushdata-length parser. Keep this aligned with Script.decodeStream
157
+ // so byte-preserving walkers can skip pushed data without semantic decode/re-encode.
158
+ /**
159
+ * Returns the pushed-data length for a push opcode.
160
+ * @param op - opcode byte already read from the script stream
161
+ * @param read - callback that reads the following 1/2/4-byte little-endian length
162
+ * @returns Push length for data-carrying opcodes, or `undefined` for non-push opcodes.
163
+ * @throws If the opcode falls through the recognized push-opcode set unexpectedly.
164
+ * {@link Error}
165
+ */
166
+ export const scriptPushLen = (op, read) => {
167
+ if (!(OP.OP_0 < op && op <= OP.PUSHDATA4))
168
+ return;
169
+ if (op < OP.PUSHDATA1)
170
+ return op;
171
+ if (op === OP.PUSHDATA1)
172
+ return read(1);
173
+ if (op === OP.PUSHDATA2)
174
+ return read(2);
175
+ if (op === OP.PUSHDATA4)
176
+ return read(4);
177
+ throw new Error('Should be not possible');
178
+ };
99
179
  // Converts script bytes to parsed script
100
180
  // 5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae
101
181
  // =>
@@ -105,13 +185,28 @@ export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
105
185
  // 030000000000000000000000000000000000000000000000000000000000000003
106
186
  // OP_3
107
187
  // CHECKMULTISIG
108
- export const Script = P.wrap({
188
+ // This is a semantic AST codec, not a byte-preserving parser: encode
189
+ // canonicalizes small integers to OP_n and chooses the shortest push opcode,
190
+ // so decode(...)+encode(...) rewrites non-minimal push spellings.
191
+ /**
192
+ * Bitcoin script coder.
193
+ * @example
194
+ * Encode a short script from opcode mnemonics and small integers.
195
+ * ```ts
196
+ * Script.encode(['OP_1', 'OP_2']);
197
+ * ```
198
+ */
199
+ export const Script = /* @__PURE__ */ (() => Object.freeze(P.wrap({
109
200
  encodeStream: (w, value) => {
201
+ aarray(value, 'value');
110
202
  for (let o of value) {
111
203
  if (typeof o === 'string') {
112
- if (OP[o] === undefined)
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')
113
208
  throw new Error(`Unknown opcode=${o}`);
114
- w.byte(OP[o]);
209
+ w.byte(op);
115
210
  continue;
116
211
  }
117
212
  else if (typeof o === 'number') {
@@ -119,6 +214,12 @@ export const Script = P.wrap({
119
214
  w.byte(0x00);
120
215
  continue;
121
216
  }
217
+ else if (o === -1) {
218
+ // BIP62 minimal push / number rules spell numeric -1 as OP_1NEGATE
219
+ // instead of a pushed ScriptNum payload 0x81.
220
+ w.byte(OP['1NEGATE']);
221
+ continue;
222
+ }
122
223
  else if (1 <= o && o <= 16) {
123
224
  w.byte(OP.OP_1 - 1 + o);
124
225
  continue;
@@ -127,8 +228,7 @@ export const Script = P.wrap({
127
228
  // Encode big numbers
128
229
  if (typeof o === 'number')
129
230
  o = ScriptNum().encode(BigInt(o));
130
- if (!isBytes(o))
131
- throw new Error(`Wrong Script OP=${o} (${typeof o})`);
231
+ abytes(o, undefined, 'value');
132
232
  // Bytes
133
233
  const len = o.length;
134
234
  if (len < OP.PUSHDATA1)
@@ -152,19 +252,14 @@ export const Script = P.wrap({
152
252
  const out = [];
153
253
  while (!r.isEnd()) {
154
254
  const cur = r.byte();
155
- // if 0 < cur < 78
156
- if (OP.OP_0 < cur && cur <= OP.PUSHDATA4) {
157
- let len;
158
- if (cur < OP.PUSHDATA1)
159
- len = cur;
160
- else if (cur === OP.PUSHDATA1)
161
- len = P.U8.decodeStream(r);
162
- else if (cur === OP.PUSHDATA2)
163
- len = P.U16LE.decodeStream(r);
164
- else if (cur === OP.PUSHDATA4)
165
- len = P.U32LE.decodeStream(r);
166
- else
167
- throw new Error('Should be not possible');
255
+ const len = scriptPushLen(cur, (bytes) => {
256
+ if (bytes === 1)
257
+ return P.U8.decodeStream(r);
258
+ if (bytes === 2)
259
+ return P.U16LE.decodeStream(r);
260
+ return P.U32LE.decodeStream(r);
261
+ });
262
+ if (len !== undefined) {
168
263
  out.push(r.bytes(len));
169
264
  }
170
265
  else if (cur === 0x00) {
@@ -182,81 +277,209 @@ export const Script = P.wrap({
182
277
  }
183
278
  return out;
184
279
  },
185
- });
186
- // BTC specific variable length integer encoding
187
- // https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
188
- const CSLimits = {
189
- 0xfd: [0xfd, 2, 253n, 65535n],
190
- 0xfe: [0xfe, 4, 65536n, 4294967295n],
191
- 0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
192
- };
193
- export const CompactSize = P.wrap({
194
- encodeStream: (w, value) => {
195
- if (typeof value === 'number')
196
- value = BigInt(value);
197
- if (0n <= value && value <= 252n)
198
- return w.byte(Number(value));
199
- for (const [flag, bytes, start, stop] of Object.values(CSLimits)) {
200
- if (start > value || value > stop)
201
- continue;
202
- w.byte(flag);
280
+ })))();
281
+ /**
282
+ * Bitcoin CompactSize integer coder.
283
+ * @example
284
+ * Encode a CompactSize integer for wire serialization.
285
+ * ```ts
286
+ * CompactSize.encode(1n);
287
+ * ```
288
+ */
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;
203
321
  for (let i = 0; i < bytes; i++)
204
- w.byte(Number((value >> (8n * BigInt(i))) & 0xffn));
205
- return;
206
- }
207
- throw w.err(`VarInt too big: ${value}`);
208
- },
209
- decodeStream: (r) => {
210
- const b0 = r.byte();
211
- if (b0 <= 0xfc)
212
- return BigInt(b0);
213
- const [_, bytes, start] = CSLimits[b0];
214
- let num = 0n;
215
- for (let i = 0; i < bytes; i++)
216
- num |= BigInt(r.byte()) << (8n * BigInt(i));
217
- if (num < start)
218
- throw r.err(`Wrong CompactSize(${8 * bytes})`);
219
- return num;
220
- },
221
- });
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
+ })();
222
331
  // Same thing, but in number instead of bigint. Checks for safe integer inside
223
- export const CompactSizeLen = P.apply(CompactSize, P.coders.numberBigint);
332
+ /**
333
+ * CompactSize coder that decodes into JavaScript numbers.
334
+ * @example
335
+ * Use the number-based CompactSize helper when the value fits a JS number.
336
+ * ```ts
337
+ * CompactSizeLen.encode(1);
338
+ * ```
339
+ */
340
+ export const CompactSizeLen = /* @__PURE__ */ (() => Object.freeze(P.apply(CompactSize, P.coders.numberBigint)))();
224
341
  // ui8a of size <CompactSize>
225
- export const VarBytes = P.bytes(CompactSize);
342
+ // Keep an unwrapped local coder for raw transaction structs. The exported VarBytes surface uses
343
+ // TRet for declaration stability, but RawInput/RawOutput still need the old internal field shape.
344
+ const _VarBytes = /* @__PURE__ */ (() => Object.freeze(P.bytes(CompactSize)))();
345
+ /**
346
+ * Length-prefixed byte array coder.
347
+ * @example
348
+ * Prefix a byte array with its CompactSize length.
349
+ * ```ts
350
+ * VarBytes.encode(new Uint8Array([1, 2, 3]));
351
+ * ```
352
+ */
353
+ export const VarBytes = _VarBytes;
226
354
  // SegWit v0 stack of witness buffers
227
- export const RawWitness = P.array(CompactSizeLen, VarBytes);
355
+ // Raw witness serialization is reused across witness versions and PSBT
356
+ // finalScriptWitness values; version-specific stack and element limits are
357
+ // enforced by the spending rules, not by this byte codec.
358
+ // Same split for raw witness stacks: export the wrapped surface, but keep the raw tx struct coder
359
+ // on the unwrapped local alias so witness arrays inside RawTx keep their previous internal shape.
360
+ const _RawWitness = /* @__PURE__ */ (() => Object.freeze(P.array(CompactSizeLen, _VarBytes)))();
361
+ /**
362
+ * SegWit witness stack coder.
363
+ * @example
364
+ * Encode one witness stack for a SegWit input.
365
+ * ```ts
366
+ * RawWitness.encode([new Uint8Array([1])]);
367
+ * ```
368
+ */
369
+ export const RawWitness = _RawWitness;
228
370
  // Array of size <CompactSize>
371
+ /**
372
+ * Coder for CompactSize-prefixed arrays.
373
+ * @param t - element coder
374
+ * @returns Array coder.
375
+ * @example
376
+ * CompactSize-prefix a small list of fixed-width integers.
377
+ * ```ts
378
+ * import * as P from 'micro-packed';
379
+ * import { BTCArray } from '@scure/btc-signer/script.js';
380
+ * BTCArray(P.U8).encode([1, 2, 3]);
381
+ * ```
382
+ */
229
383
  export const BTCArray = (t) => P.array(CompactSize, t);
230
- export const RawInput = P.struct({
384
+ /**
385
+ * Raw Bitcoin transaction input coder.
386
+ * @example
387
+ * Encode one transaction input exactly as it appears on the wire.
388
+ * ```ts
389
+ * import { hex } from '@scure/base';
390
+ * import { RawInput } from '@scure/btc-signer/script.js';
391
+ * RawInput.encode({
392
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
393
+ * index: 0,
394
+ * finalScriptSig: new Uint8Array([0x51]),
395
+ * sequence: 0xffffffff,
396
+ * });
397
+ * ```
398
+ */
399
+ export const RawInput = /* @__PURE__ */ (() => Object.freeze(P.struct({
231
400
  txid: P.bytes(32, true), // hash(prev_tx),
232
401
  index: P.U32LE, // output number of previous tx
233
- finalScriptSig: VarBytes, // btc merges input and output script, executes it. If ok = tx passes
402
+ finalScriptSig: _VarBytes, // btc merges input and output script, executes it. If ok = tx passes
234
403
  sequence: P.U32LE, // ?
235
- });
236
- export const RawOutput = P.struct({ amount: P.U64LE, script: VarBytes });
404
+ })))();
405
+ /**
406
+ * Raw Bitcoin transaction output coder.
407
+ * @example
408
+ * Encode one transaction output with amount and scriptPubKey.
409
+ * ```ts
410
+ * import { RawOutput } from '@scure/btc-signer/script.js';
411
+ * RawOutput.encode({ amount: 1n, script: new Uint8Array([0x51]) });
412
+ * ```
413
+ */
414
+ export const RawOutput = /* @__PURE__ */ (() => Object.freeze(P.struct({ amount: P.U64LE, script: _VarBytes })))();
237
415
  // https://en.bitcoin.it/wiki/Protocol_documentation#tx
238
- const _RawTx = P.struct({
416
+ const _RawTx = /* @__PURE__ */ (() => P.struct({
239
417
  version: P.I32LE,
240
418
  segwitFlag: P.flag(new Uint8Array([0x00, 0x01])),
241
419
  inputs: BTCArray(RawInput),
242
420
  outputs: BTCArray(RawOutput),
243
- witnesses: P.flagged('segwitFlag', P.array('inputs/length', RawWitness)),
421
+ // BIP144 does not encode a witness-count field; one RawWitness entry is
422
+ // implied for each txin and follows the same order as inputs.
423
+ witnesses: P.flagged('segwitFlag', P.array('inputs/length', _RawWitness)),
244
424
  // < 500000000 Block number at which this transaction is unlocked
245
425
  // >= 500000000 UNIX timestamp at which this transaction is unlocked
246
426
  // Handled as part of PSBTv2
247
427
  lockTime: P.U32LE,
248
- });
428
+ }))();
249
429
  function validateRawTx(tx) {
250
- if (tx.segwitFlag && tx.witnesses && !tx.witnesses.length)
251
- throw new Error('Segwit flag with empty witnesses array');
430
+ // BIP 144: if every per-input witness field is empty, callers must use the old
431
+ // serialization format instead of the marker/flag witness form.
432
+ if (tx.segwitFlag && tx.witnesses && tx.witnesses.every((w) => !w.length))
433
+ throw new Error('Segwit flag with only empty witness fields');
252
434
  return tx;
253
435
  }
254
- export const RawTx = P.validate(_RawTx, validateRawTx);
436
+ /**
437
+ * Raw Bitcoin transaction coder.
438
+ * @example
439
+ * Encode a SegWit transaction with one input, one output, and one witness stack.
440
+ * ```ts
441
+ * import { hex } from '@scure/base';
442
+ * import { RawTx } from '@scure/btc-signer/script.js';
443
+ * RawTx.encode({
444
+ * version: 2,
445
+ * segwitFlag: true,
446
+ * inputs: [{
447
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
448
+ * index: 0,
449
+ * finalScriptSig: new Uint8Array(),
450
+ * sequence: 0xffffffff,
451
+ * }],
452
+ * outputs: [{ amount: 1n, script: new Uint8Array([0x51]) }],
453
+ * witnesses: [[new Uint8Array([1])]],
454
+ * lockTime: 0,
455
+ * });
456
+ * ```
457
+ */
458
+ export const RawTx = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawTx, validateRawTx)))();
255
459
  // Pre-SegWit serialization format (for PSBTv0)
256
- export const RawOldTx = P.struct({
460
+ /**
461
+ * Pre-SegWit transaction coder used by PSBTv0.
462
+ * @example
463
+ * Encode the legacy unsigned transaction format used inside PSBTv0 globals.
464
+ * ```ts
465
+ * import { hex } from '@scure/base';
466
+ * import { RawOldTx } from '@scure/btc-signer/script.js';
467
+ * RawOldTx.encode({
468
+ * version: 2,
469
+ * inputs: [{
470
+ * txid: hex.decode('0000000000000000000000000000000000000000000000000000000000000001'),
471
+ * index: 0,
472
+ * finalScriptSig: new Uint8Array(),
473
+ * sequence: 0xffffffff,
474
+ * }],
475
+ * outputs: [{ amount: 1n, script: new Uint8Array([0x51]) }],
476
+ * lockTime: 0,
477
+ * });
478
+ * ```
479
+ */
480
+ export const RawOldTx = /* @__PURE__ */ (() => Object.freeze(P.struct({
257
481
  version: P.I32LE,
258
482
  inputs: BTCArray(RawInput),
259
483
  outputs: BTCArray(RawOutput),
260
484
  lockTime: P.U32LE,
261
- });
262
- //# sourceMappingURL=script.js.map
485
+ })))();
@@ -0,0 +1,69 @@
1
+ import { secp256k1 as secp } from '@noble/curves/secp256k1.js';
2
+ import { hex } from '@scure/base';
3
+ import * as btc from './index.ts';
4
+
5
+ const privKey1 = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
6
+ const P1 = secp.getPublicKey(privKey1, true);
7
+
8
+ const wpkh = btc.p2wpkh(P1);
9
+
10
+ const tx = new btc.Transaction();
11
+
12
+ // Basic input test
13
+ tx.addInput({
14
+ txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
15
+ index: 0,
16
+ ...wpkh,
17
+ finalScriptSig: Uint8Array.of(),
18
+ sequence: 1,
19
+ });
20
+
21
+ // Doesn't force any fields on input addition (only on sign)
22
+ tx.addInput({
23
+ sequence: 1,
24
+ });
25
+
26
+ tx.updateInput(0, {
27
+ sequence: 1,
28
+ });
29
+
30
+ const nonWitnessUtxo =
31
+ '0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000';
32
+ const nonWitnessUtxoB = hex.decode(nonWitnessUtxo);
33
+
34
+ tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxo });
35
+ tx.updateInput(0, { nonWitnessUtxo: nonWitnessUtxoB });
36
+ tx.addInput({
37
+ txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
38
+ index: 0,
39
+ nonWitnessUtxo: nonWitnessUtxo,
40
+ });
41
+
42
+ tx.addInput({
43
+ txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
44
+ index: 0,
45
+ nonWitnessUtxo: nonWitnessUtxoB,
46
+ });
47
+
48
+ // Should fail!
49
+ // tx.updateInput(0, {
50
+ // nonWitnessUtxo: 1,
51
+ // });
52
+ // Outputs
53
+ tx.addOutput({ amount: BigInt(123) });
54
+ // should fail
55
+ // tx.updateOutput(0, { amount: '1' });
56
+ // tx.updateOutput(0, { amount: 1 });
57
+ // should fail
58
+ // tx.addOutput({ amount: '123' });
59
+ // tx.addOutput({ amount: 123 });
60
+
61
+ for (let i = 0; i < tx.inputsLength; i++) {
62
+ // @ts-ignore
63
+ console.log('I', tx.getInput(i));
64
+ }
65
+
66
+ for (let i = 0; i < tx.outputsLength; i++) {
67
+ // @ts-ignore
68
+ console.log('O', tx.getOutput(i));
69
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  /*! scure-btc-signer - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2
+ import type { TArg, TRet } from './utils.ts';
2
3
  import {
3
4
  compareBytes,
4
5
  concatBytes,
@@ -25,16 +26,36 @@ export {
25
26
  export type { ScriptType } from './script.ts';
26
27
  export { getInputType, Transaction } from './transaction.ts';
27
28
  export { NETWORK, TAPROOT_UNSPENDABLE_KEY, TEST_NETWORK } from './utils.ts';
29
+ export type { TArg, TRet } from './utils.ts';
28
30
  export { selectUTXO } from './utxo.ts';
29
31
 
30
- export const utils = {
31
- isBytes,
32
- concatBytes,
33
- compareBytes,
34
- pubSchnorr,
35
- randomPrivateKeyBytes,
36
- taprootTweakPubkey,
37
- };
32
+ /**
33
+ * Small collection of commonly used utility exports.
34
+ * @example
35
+ * Reach for the grouped helpers when you want the common byte and Taproot utilities.
36
+ * ```ts
37
+ * utils.compareBytes(new Uint8Array([1]), new Uint8Array([2]));
38
+ * ```
39
+ */
40
+ // Convenience subset; import from `./utils.ts` when you need the full helper surface.
41
+ export const utils: TRet<
42
+ Readonly<{
43
+ isBytes: (a: unknown) => a is Uint8Array;
44
+ concatBytes: (...arrays: TArg<Uint8Array[]>) => TRet<Uint8Array>;
45
+ compareBytes: typeof compareBytes;
46
+ pubSchnorr: (priv: TArg<Uint8Array>) => TRet<Uint8Array>;
47
+ randomPrivateKeyBytes: () => TRet<Uint8Array>;
48
+ taprootTweakPubkey: typeof taprootTweakPubkey;
49
+ }>
50
+ > = /* @__PURE__ */ (() =>
51
+ Object.freeze({
52
+ isBytes,
53
+ concatBytes,
54
+ compareBytes,
55
+ pubSchnorr,
56
+ randomPrivateKeyBytes,
57
+ taprootTweakPubkey,
58
+ }))();
38
59
 
39
60
  export {
40
61
  _sortPubkeys,
@@ -45,9 +66,11 @@ export {
45
66
  sortedMultisig,
46
67
  taprootListToTree,
47
68
  WIF,
48
- } from './payment.ts'; // remove
69
+ } from './payment.ts';
49
70
  // remove
50
71
  export type { CustomScript, OptScript } from './payment.ts';
51
- export { _DebugPSBT, TaprootControlBlock } from './psbt.ts'; // remove
52
- export { bip32Path, Decimal, DEFAULT_SEQUENCE, PSBTCombine, SigHash } from './transaction.ts'; // remove
72
+ // remove
73
+ export { _DebugPSBT, TaprootControlBlock } from './psbt.ts';
74
+ // remove
75
+ export { bip32Path, Decimal, DEFAULT_SEQUENCE, PSBTCombine, SigHash } from './transaction.ts';
53
76
  export { _cmpBig, _Estimator } from './utxo.ts';