@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/README.md +334 -64
- package/index.d.ts +15 -6
- package/index.js +16 -7
- package/musig2.d.ts +212 -69
- package/musig2.js +352 -99
- package/net.d.ts +355 -0
- package/net.js +875 -0
- package/p2p.d.ts +17 -8
- package/p2p.js +63 -11
- package/package.json +17 -17
- package/payment.d.ts +406 -41
- package/payment.js +570 -69
- package/psbt.d.ts +2958 -560
- package/psbt.js +475 -119
- package/script.d.ts +311 -133
- package/script.js +313 -90
- package/src/_type_test.ts +69 -0
- package/src/index.ts +34 -11
- package/src/musig2.ts +424 -155
- package/src/net.ts +1106 -0
- package/src/p2p.ts +76 -24
- package/src/payment.ts +882 -235
- package/src/psbt.ts +648 -229
- package/src/script.ts +397 -139
- package/src/transaction.ts +667 -196
- package/src/utils.ts +392 -47
- package/src/utxo.ts +182 -83
- package/transaction.d.ts +242 -32
- package/transaction.js +531 -121
- package/utils.d.ts +296 -25
- package/utils.js +337 -30
- package/utxo.d.ts +438 -76
- package/utxo.js +150 -59
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/musig2.d.ts.map +0 -1
- package/musig2.js.map +0 -1
- package/p2p.d.ts.map +0 -1
- package/p2p.js.map +0 -1
- package/payment.d.ts.map +0 -1
- package/payment.js.map +0 -1
- package/psbt.d.ts.map +0 -1
- package/psbt.js.map +0 -1
- package/script.d.ts.map +0 -1
- package/script.js.map +0 -1
- package/transaction.d.ts.map +0 -1
- package/transaction.js.map +0 -1
- package/utils.d.ts.map +0 -1
- package/utils.js.map +0 -1
- package/utxo.d.ts.map +0 -1
- package/utxo.js.map +0 -1
package/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
|
-
|
|
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
|
-
|
|
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 ===
|
|
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 >>=
|
|
47
|
-
nums.push(Number(abs &
|
|
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
|
|
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[
|
|
107
|
+
if ((data[len - 1] & 0x7f) === 0) {
|
|
64
108
|
// exception
|
|
65
|
-
if (len <= 1 || (data[
|
|
109
|
+
if (len <= 1 || (data[len - 2] & 0x80) === 0)
|
|
66
110
|
throw new Error('Non-minimally encoded ScriptNum');
|
|
67
111
|
}
|
|
68
112
|
}
|
|
69
|
-
let
|
|
70
|
-
let
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
res
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
402
|
+
finalScriptSig: _VarBytes, // btc merges input and output script, executes it. If ok = tx passes
|
|
234
403
|
sequence: P.U32LE, // ?
|
|
235
|
-
});
|
|
236
|
-
|
|
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
|
-
|
|
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
|
|
251
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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';
|
|
69
|
+
} from './payment.ts';
|
|
49
70
|
// remove
|
|
50
71
|
export type { CustomScript, OptScript } from './payment.ts';
|
|
51
|
-
|
|
52
|
-
export {
|
|
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';
|