@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/README.md +189 -39
- package/index.d.ts +15 -5
- package/index.d.ts.map +1 -1
- package/index.js +16 -6
- package/index.js.map +1 -1
- package/musig2.d.ts +202 -64
- package/musig2.d.ts.map +1 -1
- package/musig2.js +324 -87
- package/musig2.js.map +1 -1
- package/p2p.d.ts +17 -7
- package/p2p.d.ts.map +1 -1
- package/p2p.js +40 -4
- package/p2p.js.map +1 -1
- package/package.json +25 -10
- package/payment.d.ts +407 -38
- package/payment.d.ts.map +1 -1
- package/payment.js +504 -57
- package/payment.js.map +1 -1
- package/psbt.d.ts +2958 -559
- package/psbt.d.ts.map +1 -1
- package/psbt.js +462 -118
- package/psbt.js.map +1 -1
- package/script.d.ts +311 -132
- package/script.d.ts.map +1 -1
- package/script.js +246 -35
- package/script.js.map +1 -1
- package/src/index.ts +34 -11
- package/src/musig2.ts +387 -145
- package/src/p2p.ts +54 -18
- package/src/payment.ts +823 -226
- package/src/psbt.ts +633 -228
- package/src/script.ts +353 -117
- package/src/transaction.ts +593 -169
- package/src/utils.ts +322 -43
- package/src/utxo.ts +154 -51
- package/transaction.d.ts +242 -31
- package/transaction.d.ts.map +1 -1
- package/transaction.js +460 -100
- package/transaction.js.map +1 -1
- package/utils.d.ts +266 -24
- package/utils.d.ts.map +1 -1
- package/utils.js +278 -27
- package/utils.js.map +1 -1
- package/utxo.d.ts +438 -75
- package/utxo.d.ts.map +1 -1
- package/utxo.js +123 -36
- package/utxo.js.map +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
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 =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
+
))();
|