@scure/btc-signer 2.2.0 → 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 +148 -28
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/musig2.d.ts +11 -6
- package/musig2.js +28 -12
- package/net.d.ts +355 -0
- package/net.js +875 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +15 -19
- package/payment.d.ts +2 -6
- package/payment.js +85 -31
- package/psbt.d.ts +0 -1
- package/psbt.js +18 -6
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +69 -0
- package/src/musig2.ts +39 -12
- package/src/net.ts +1106 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +88 -38
- package/src/psbt.ts +19 -5
- package/src/script.ts +57 -35
- package/src/transaction.ts +81 -34
- package/src/utils.ts +68 -2
- package/src/utxo.ts +39 -43
- package/transaction.d.ts +0 -1
- package/transaction.js +81 -31
- package/utils.d.ts +30 -1
- package/utils.js +59 -3
- package/utxo.d.ts +0 -1
- package/utxo.js +36 -32
- 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/p2p.d.ts
CHANGED
|
@@ -44,4 +44,3 @@ export declare const elligatorSwift: Readonly<{
|
|
|
44
44
|
getSharedSecret: (privateKeyA: TArg<Uint8Array>, publicKeyB: TArg<Uint8Array>) => TRet<Bytes>;
|
|
45
45
|
getSharedSecretBip324: (privateKeyOurs: TArg<Uint8Array>, publicKeyTheirs: TArg<Uint8Array>, publicKeyOurs: TArg<Uint8Array>, initiating: boolean) => TRet<Uint8Array>;
|
|
46
46
|
}>;
|
|
47
|
-
//# sourceMappingURL=p2p.d.ts.map
|
package/p2p.js
CHANGED
|
@@ -39,6 +39,9 @@ const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
|
|
|
39
39
|
const _3n = BigInt(3);
|
|
40
40
|
const _4n = BigInt(4);
|
|
41
41
|
const _7n = BigInt(7);
|
|
42
|
+
// Precomputed 1/2 mod p: turns the frequent "divide by 2" steps of XSwiftEC/XSwiftECInv
|
|
43
|
+
// into single multiplications instead of one modular inversion per call.
|
|
44
|
+
const INV_2 = Fp.inv(Fp.create(_2n));
|
|
42
45
|
// This is the "lift_x(x) succeeds" predicate for field-normalized x values.
|
|
43
46
|
// Raw x >= p would need the full BIP340 range check before reducing modulo p.
|
|
44
47
|
const isValidX = (x) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
|
|
@@ -93,7 +96,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
93
96
|
return; // [2 condition]
|
|
94
97
|
if (ellCase & 1 && Fp.is0(r))
|
|
95
98
|
return;
|
|
96
|
-
v = Fp.
|
|
99
|
+
v = Fp.mul(Fp.add(Fp.neg(u), Fp.div(r, s)), INV_2); // v = (-u + r / s) / 2
|
|
97
100
|
}
|
|
98
101
|
const w = trySqrt(s);
|
|
99
102
|
if (w === undefined)
|
|
@@ -102,7 +105,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
102
105
|
const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
|
|
103
106
|
const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
|
|
104
107
|
// w0 * (u * t0 / 2 + v)
|
|
105
|
-
return Fp.mul(w0, Fp.add(Fp.
|
|
108
|
+
return Fp.mul(w0, Fp.add(Fp.mul(Fp.mul(u, t0), INV_2), v));
|
|
106
109
|
},
|
|
107
110
|
// Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
|
|
108
111
|
// BIP324 samples encodings for x(P), so callers must pass a curve X coordinate in 0..p-1;
|
|
@@ -112,11 +115,19 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
112
115
|
// so encode() must reject out-of-range x instead of silently reducing a different bigint modulo p.
|
|
113
116
|
if (!Fp.isValid(x))
|
|
114
117
|
throw new RangeError('elligatorSwift.encode: expected x coordinate in range 0..p-1');
|
|
118
|
+
// Off-curve x cannot round-trip: decode() only returns lift_x-able candidates, so
|
|
119
|
+
// the loop below would silently emit an encoding of a *different* public key.
|
|
120
|
+
if (!isValidX(x))
|
|
121
|
+
throw new RangeError('elligatorSwift.encode: expected x coordinate of a curve point');
|
|
115
122
|
// 200k test cycles per keygen: avg=4 max=48
|
|
116
123
|
// seems too much, but same as for reference implementation
|
|
117
124
|
while (true) {
|
|
118
|
-
//
|
|
119
|
-
|
|
125
|
+
// Random field element 1..p-1: BIP324 samples u over the whole field (the previous
|
|
126
|
+
// secret-key sampler silently restricted u to 1..n-1); decode() remaps u = 0, so
|
|
127
|
+
// zero cannot round-trip and is skipped.
|
|
128
|
+
const u = Fp.create(Fp.fromBytes(randomBytes(32), true));
|
|
129
|
+
if (Fp.is0(u))
|
|
130
|
+
continue;
|
|
120
131
|
const ellCase = randomBytes(1)[0] & 7; // [0..8)
|
|
121
132
|
const t = elligatorSwift._inv(x, u, ellCase);
|
|
122
133
|
if (!t)
|
|
@@ -148,10 +159,12 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
148
159
|
let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
|
|
149
160
|
if (isValidX(res))
|
|
150
161
|
return Fp.toBytes(res);
|
|
151
|
-
|
|
162
|
+
// X / Y is shared by the remaining candidates; computing it once saves an inversion.
|
|
163
|
+
const xDivY = Fp.div(x, y);
|
|
164
|
+
res = Fp.mul(Fp.sub(Fp.neg(xDivY), u), INV_2); // (-X / Y - u) / 2
|
|
152
165
|
if (isValidX(res))
|
|
153
166
|
return Fp.toBytes(res);
|
|
154
|
-
res = Fp.
|
|
167
|
+
res = Fp.mul(Fp.sub(xDivY, u), INV_2); // (X / Y - u) / 2
|
|
155
168
|
if (isValidX(res))
|
|
156
169
|
return Fp.toBytes(res);
|
|
157
170
|
throw new Error('elligatorSwift: cannot decode public key');
|
|
@@ -178,6 +191,10 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
178
191
|
getSharedSecretBip324: (privateKeyOurs, publicKeyTheirs, publicKeyOurs, initiating) => {
|
|
179
192
|
// BIP324 Shared secret computation hashes "the exactly 64-byte public keys'
|
|
180
193
|
// encodings sent over the wire", so both ElligatorSwift inputs must be 64 bytes here.
|
|
194
|
+
// Initiator/responder ordering decides the hash-input order, so require a real boolean
|
|
195
|
+
// instead of letting arbitrary truthy values pick a side.
|
|
196
|
+
if (typeof initiating !== 'boolean')
|
|
197
|
+
throw new TypeError('"initiating" expected boolean, got type=' + typeof initiating);
|
|
181
198
|
const ours = abytes(publicKeyOurs, 64, 'publicKeyOurs');
|
|
182
199
|
const theirs = abytes(publicKeyTheirs, 64, 'publicKeyTheirs');
|
|
183
200
|
const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
|
|
@@ -185,4 +202,3 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
185
202
|
return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
|
|
186
203
|
},
|
|
187
204
|
});
|
|
188
|
-
//# sourceMappingURL=p2p.js.map
|
package/package.json
CHANGED
|
@@ -1,46 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scure/btc-signer",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Audited & minimal library for Bitcoin. Handle transactions, Schnorr, Taproot, UTXO & PSBT",
|
|
5
5
|
"files": [
|
|
6
6
|
"*.js",
|
|
7
7
|
"*.d.ts",
|
|
8
|
-
"*.js.map",
|
|
9
|
-
"*.d.ts.map",
|
|
10
8
|
"src",
|
|
11
9
|
"!_type_test.*"
|
|
12
10
|
],
|
|
13
11
|
"dependencies": {
|
|
14
|
-
"@noble/curves": "~2.
|
|
15
|
-
"@noble/hashes": "~2.
|
|
16
|
-
"@scure/base": "~2.
|
|
17
|
-
"micro-packed": "~0.
|
|
12
|
+
"@noble/curves": "~2.3.0",
|
|
13
|
+
"@noble/hashes": "~2.3.0",
|
|
14
|
+
"@scure/base": "~2.3.0",
|
|
15
|
+
"micro-packed": "~0.11.0"
|
|
18
16
|
},
|
|
19
17
|
"devDependencies": {
|
|
20
|
-
"@paulmillr/jsbt": "0.5
|
|
21
|
-
"@scure/bip32": "2.
|
|
18
|
+
"@paulmillr/jsbt": "0.6.5",
|
|
19
|
+
"@scure/bip32": "2.2.0",
|
|
20
|
+
"micro-ftch": "^1.1.0",
|
|
22
21
|
"prettier": "3.6.2",
|
|
23
22
|
"typescript": "6.0.2"
|
|
24
23
|
},
|
|
25
24
|
"scripts": {
|
|
25
|
+
"benchmark:size": "npx bismar@0.1.3 -s",
|
|
26
26
|
"build": "tsc",
|
|
27
|
-
"build:clean": "rm -f *.{js,d.ts
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"check:treeshake": "npx --no @paulmillr/jsbt treeshake package.json test/build/out-treeshake",
|
|
32
|
-
"check:jsdoc": "npx --no @paulmillr/jsbt tsdoc package.json",
|
|
27
|
+
"build:clean": "rm -f *.{js,d.ts}",
|
|
28
|
+
"check": "jsbt-check",
|
|
29
|
+
"benchmark": "node benchmark/index.ts",
|
|
30
|
+
"benchmark:network": "node benchmark/network.ts",
|
|
33
31
|
"format": "prettier --write src test/*.test.*",
|
|
34
|
-
"test": "node
|
|
35
|
-
"test:bun": "bun test/index.ts",
|
|
36
|
-
"test:deno": "deno --allow-env --allow-read test/index.ts",
|
|
37
|
-
"test:node20": "cd test; npx tsc; node compiled/test/index.js",
|
|
32
|
+
"test": "node test/index.ts",
|
|
38
33
|
"test:slow": "node test/slow.test.ts",
|
|
39
34
|
"test:extended": "node --experimental-loader ./test/bitcoinjs-test/esm-loader.js ./test/bitcoinjs-test/index.test.js"
|
|
40
35
|
},
|
|
41
36
|
"exports": {
|
|
42
37
|
".": "./index.js",
|
|
43
38
|
"./musig2.js": "./musig2.js",
|
|
39
|
+
"./net.js": "./net.js",
|
|
44
40
|
"./p2p.js": "./p2p.js",
|
|
45
41
|
"./payment.js": "./payment.js",
|
|
46
42
|
"./psbt.js": "./psbt.js",
|
package/payment.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ export type CustomScript = Coder<OptScript, CustomScriptOut | undefined> & {
|
|
|
91
91
|
export declare const OutScript: TRet<P.CoderType<NonNullable<OutP2AType | OutPKType | OutPKHType | OutSHType | OutWSHType | OutWPKHType | OutMSType | OutTRType | OutTRNSType | OutTRMSType | OutUnknownType | undefined>>>;
|
|
92
92
|
/** Type of the output-script coder. */
|
|
93
93
|
export type OutScriptType = typeof OutScript;
|
|
94
|
-
type
|
|
94
|
+
type AddressValue = NonNullable<ReturnType<OutScriptType['decode']>>;
|
|
95
95
|
/**
|
|
96
96
|
* Validates that nested redeem and witness scripts match their wrappers.
|
|
97
97
|
* @param script - top-level output script
|
|
@@ -561,9 +561,5 @@ export declare function WIF(network?: BTC_NETWORK): TRet<Coder<Bytes, string>>;
|
|
|
561
561
|
* coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
|
|
562
562
|
* ```
|
|
563
563
|
*/
|
|
564
|
-
export declare function Address(network?: BTC_NETWORK):
|
|
565
|
-
encode(from: Exclude<OutScriptValue, undefined>): string;
|
|
566
|
-
decode(address: string): OutScriptValue;
|
|
567
|
-
};
|
|
564
|
+
export declare function Address(network?: BTC_NETWORK): TRet<P.Coder<AddressValue, string>>;
|
|
568
565
|
export {};
|
|
569
|
-
//# sourceMappingURL=payment.d.ts.map
|
package/payment.js
CHANGED
|
@@ -6,10 +6,16 @@ import { TaprootControlBlock } from "./psbt.js";
|
|
|
6
6
|
import { MAX_SCRIPT_BYTE_LENGTH, OpToNum, Script, VarBytes } from "./script.js";
|
|
7
7
|
import * as u from "./utils.js";
|
|
8
8
|
import { NETWORK } from "./utils.js";
|
|
9
|
+
// Pay to Anchor (P2A)
|
|
10
|
+
// BIP433 Pay-to-Anchor witness program bytes; the scriptPubKey is `OP_1 <0x4e73>`.
|
|
11
|
+
const P2A_PROGRAM = /* @__PURE__ */ Uint8Array.from([0x4e, 0x73]);
|
|
9
12
|
const OutP2A = {
|
|
10
13
|
encode(from) {
|
|
11
14
|
// BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
|
|
12
|
-
if (from.length !== 2 ||
|
|
15
|
+
if (from.length !== 2 ||
|
|
16
|
+
from[0] !== 1 ||
|
|
17
|
+
!u.isBytes(from[1]) ||
|
|
18
|
+
!u.equalBytes(from[1], P2A_PROGRAM))
|
|
13
19
|
return;
|
|
14
20
|
return { type: 'p2a', script: Script.encode(from) };
|
|
15
21
|
},
|
|
@@ -18,7 +24,7 @@ const OutP2A = {
|
|
|
18
24
|
return;
|
|
19
25
|
// The decoded object keeps `script` for caller convenience, but the `p2a`
|
|
20
26
|
// tag always canonicalizes back to the fixed BIP433 script.
|
|
21
|
-
return [1,
|
|
27
|
+
return [1, Uint8Array.from(P2A_PROGRAM)];
|
|
22
28
|
},
|
|
23
29
|
};
|
|
24
30
|
function isValidPubkey(pub, type) {
|
|
@@ -54,8 +60,10 @@ const OutPKH = {
|
|
|
54
60
|
encode(from) {
|
|
55
61
|
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
|
|
56
62
|
return;
|
|
57
|
-
//
|
|
58
|
-
//
|
|
63
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
64
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
65
|
+
if (from[2].length !== 20)
|
|
66
|
+
return;
|
|
59
67
|
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
|
|
60
68
|
return;
|
|
61
69
|
return { type: 'pkh', hash: from[2] };
|
|
@@ -70,8 +78,10 @@ const OutSH = {
|
|
|
70
78
|
encode(from) {
|
|
71
79
|
if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
72
80
|
return;
|
|
73
|
-
//
|
|
74
|
-
//
|
|
81
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
82
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
83
|
+
if (from[1].length !== 20)
|
|
84
|
+
return;
|
|
75
85
|
return { type: 'sh', hash: from[1] };
|
|
76
86
|
},
|
|
77
87
|
// OutScript validates `sh.hash` before this branch emits the canonical
|
|
@@ -119,11 +129,15 @@ const OutMS = {
|
|
|
119
129
|
const pubkeys = from.slice(1, -2);
|
|
120
130
|
if (n !== pubkeys.length)
|
|
121
131
|
return;
|
|
132
|
+
// Require valid ECDSA pubkeys and `0 < m <= n` here so near-miss
|
|
133
|
+
// CHECKMULTISIG scripts (garbage keys, degenerate 0-of-0) fall through to
|
|
134
|
+
// OutUnknown instead of throwing in the OutScript validator on decode.
|
|
135
|
+
// Script.decode only yields 0..16 for opcode numbers, so n <= 16 holds.
|
|
122
136
|
for (const pub of pubkeys)
|
|
123
|
-
if (!u.isBytes(pub))
|
|
137
|
+
if (!u.isBytes(pub) || !isValidPubkey(pub, u.PubT.ecdsa))
|
|
124
138
|
return;
|
|
125
|
-
|
|
126
|
-
|
|
139
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > n)
|
|
140
|
+
return;
|
|
127
141
|
// We don't need n here because it is the same as pubkeys.length.
|
|
128
142
|
return { type: 'ms', m, pubkeys: pubkeys };
|
|
129
143
|
},
|
|
@@ -143,6 +157,11 @@ const OutTR = {
|
|
|
143
157
|
// other OP_1 program lengths remain reserved future witness programs and should fall through.
|
|
144
158
|
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32)
|
|
145
159
|
return;
|
|
160
|
+
// A 32-byte v1 program with an off-curve x coordinate is a fundable but
|
|
161
|
+
// taproot-unspendable output; classify it as unknown instead of throwing
|
|
162
|
+
// in the OutScript validator on decode.
|
|
163
|
+
if (!isValidPubkey(from[1], u.PubT.schnorr))
|
|
164
|
+
return;
|
|
146
165
|
return { type: 'tr', pubkey: from[1] };
|
|
147
166
|
},
|
|
148
167
|
// OutScript validates `tr.pubkey` before this branch emits the canonical
|
|
@@ -206,10 +225,15 @@ const OutTRMS = {
|
|
|
206
225
|
return;
|
|
207
226
|
continue;
|
|
208
227
|
}
|
|
209
|
-
|
|
228
|
+
// Require actual Schnorr pubkeys here (same as tr_ns) so near-miss
|
|
229
|
+
// CHECKSIGADD scripts fall through to OutUnknown instead of throwing
|
|
230
|
+
// in the OutScript validator on decode.
|
|
231
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr))
|
|
210
232
|
return;
|
|
211
233
|
pubkeys.push(elm);
|
|
212
234
|
}
|
|
235
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > pubkeys.length || pubkeys.length > 999)
|
|
236
|
+
return;
|
|
213
237
|
return { type: 'tr_ms', pubkeys, m };
|
|
214
238
|
},
|
|
215
239
|
decode: (to) => {
|
|
@@ -457,6 +481,7 @@ export const p2pkh = (publicKey, network = NETWORK) => {
|
|
|
457
481
|
* ```
|
|
458
482
|
*/
|
|
459
483
|
export const p2sh = (child, network = NETWORK) => {
|
|
484
|
+
u.validateObject(child, {}, {}, 'child');
|
|
460
485
|
// It is already tested inside noble-hashes and checkScript
|
|
461
486
|
// BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
|
|
462
487
|
// limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
|
|
@@ -506,6 +531,7 @@ export const p2sh = (child, network = NETWORK) => {
|
|
|
506
531
|
* ```
|
|
507
532
|
*/
|
|
508
533
|
export const p2wsh = (child, network = NETWORK) => {
|
|
534
|
+
u.validateObject(child, {}, {}, 'child');
|
|
509
535
|
const cs = child.script;
|
|
510
536
|
if (!u.isBytes(cs))
|
|
511
537
|
throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
@@ -584,9 +610,17 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
584
610
|
// disable custom. All custom scripts for taproot should have prefix 'tr_'
|
|
585
611
|
if (customScripts) {
|
|
586
612
|
const cs = P.apply(Script, P.coders.match(customScripts));
|
|
587
|
-
|
|
613
|
+
let c;
|
|
614
|
+
// match() throws when no custom coder matches; treat that as "not a custom
|
|
615
|
+
// script" so the allowUnknownOutputs escape below stays reachable.
|
|
616
|
+
try {
|
|
617
|
+
c = cs.decode(script);
|
|
618
|
+
}
|
|
619
|
+
catch (e) {
|
|
620
|
+
c = undefined;
|
|
621
|
+
}
|
|
588
622
|
if (c !== undefined) {
|
|
589
|
-
if (
|
|
623
|
+
if (!u.astring(c.type, 'c.type').startsWith('tr_'))
|
|
590
624
|
throw new Error(`P2TR: invalid custom type=${c.type}`);
|
|
591
625
|
return;
|
|
592
626
|
}
|
|
@@ -632,6 +666,15 @@ function checkTaprootScript(script, internalPubKey, allowUnknownOutputs = false,
|
|
|
632
666
|
* ```
|
|
633
667
|
*/
|
|
634
668
|
export function taprootListToTree(taprootList) {
|
|
669
|
+
u.aarray(taprootList, 'taprootList', (leaf, title) => {
|
|
670
|
+
// p2tr reduces non-binary trees through this helper, so nested branch arrays are valid here.
|
|
671
|
+
if (Array.isArray(leaf))
|
|
672
|
+
return;
|
|
673
|
+
u.validateObject(leaf, {}, {}, title);
|
|
674
|
+
// This helper only arranges weighted tree nodes; p2tr validates leaf scripts while hashing.
|
|
675
|
+
if (leaf.weight !== undefined)
|
|
676
|
+
anumber(leaf.weight, title + '.weight');
|
|
677
|
+
});
|
|
635
678
|
// Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
|
|
636
679
|
// key-path-only outputs instead of passing [] here, otherwise this helper would return
|
|
637
680
|
// undefined and downstream taproot tree walkers would fail much later on a non-tree value.
|
|
@@ -684,20 +727,23 @@ function taprootWalkTree(tree) {
|
|
|
684
727
|
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
|
|
685
728
|
}
|
|
686
729
|
function taprootHashTree(tree, internalPubKey, allowUnknownOutputs = false, customScripts) {
|
|
687
|
-
if (
|
|
730
|
+
if (tree === undefined)
|
|
688
731
|
throw new Error('taprootHashTree: empty tree');
|
|
732
|
+
if (!Array.isArray(tree) && !P.utils.isPlainObject(tree))
|
|
733
|
+
throw new TypeError('"tree" expected object or array, got type=' + typeof tree);
|
|
689
734
|
if (Array.isArray(tree) && tree.length === 1)
|
|
690
735
|
tree = tree[0];
|
|
691
736
|
// Terminal node (leaf)
|
|
692
737
|
if (!Array.isArray(tree)) {
|
|
738
|
+
u.validateObject(tree, {}, {}, 'tree');
|
|
693
739
|
const version = tree.leafVersion;
|
|
694
740
|
const { script: leafScript } = tree;
|
|
695
741
|
// Earliest tree walk where we can validate tapScripts
|
|
696
742
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
697
743
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
698
|
-
const script = typeof leafScript === 'string'
|
|
699
|
-
|
|
700
|
-
|
|
744
|
+
const script = typeof leafScript === 'string'
|
|
745
|
+
? hex.decode(leafScript)
|
|
746
|
+
: abytes(leafScript, undefined, 'tree.script');
|
|
701
747
|
checkTaprootScript(script, internalPubKey, allowUnknownOutputs, customScripts);
|
|
702
748
|
return {
|
|
703
749
|
type: 'leaf',
|
|
@@ -766,18 +812,20 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
766
812
|
let hashedTree = taprootAddPath(taprootHashTree(tree, pubKey, allowUnknownOutputs, customScripts));
|
|
767
813
|
const tapMerkleRoot = hashedTree.hash;
|
|
768
814
|
const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
|
|
815
|
+
const tapLeafScript = [];
|
|
769
816
|
const leaves = taprootWalkTree(hashedTree).map((l) => {
|
|
770
817
|
const version = tapLeafVersion(l.version);
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
internalKey: pubKey,
|
|
778
|
-
merklePath: l.path,
|
|
779
|
-
}),
|
|
818
|
+
// Leaf versions are stored as the base even byte; only the control block adds the
|
|
819
|
+
// output-key parity bit required by BIP 341 script-path spending.
|
|
820
|
+
const controlBlock = {
|
|
821
|
+
version: version + parity,
|
|
822
|
+
internalKey: pubKey,
|
|
823
|
+
merklePath: l.path,
|
|
780
824
|
};
|
|
825
|
+
// Skip an encode/decode copy for performance; callers must treat returned metadata as
|
|
826
|
+
// immutable.
|
|
827
|
+
tapLeafScript.push([controlBlock, u.concatBytes(l.script, new Uint8Array([version]))]);
|
|
828
|
+
return { ...l, controlBlock: TaprootControlBlock.encode(controlBlock) };
|
|
781
829
|
});
|
|
782
830
|
return {
|
|
783
831
|
type: 'tr',
|
|
@@ -788,10 +836,7 @@ export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknownOutput
|
|
|
788
836
|
// PSBT stuff
|
|
789
837
|
tapInternalKey: pubKey,
|
|
790
838
|
leaves,
|
|
791
|
-
tapLeafScript
|
|
792
|
-
TaprootControlBlock.decode(l.controlBlock),
|
|
793
|
-
u.concatBytes(l.script, new Uint8Array([tapLeafVersion(l.version)])),
|
|
794
|
-
]),
|
|
839
|
+
tapLeafScript,
|
|
795
840
|
tapMerkleRoot,
|
|
796
841
|
};
|
|
797
842
|
}
|
|
@@ -938,6 +983,7 @@ export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
|
938
983
|
* ```
|
|
939
984
|
*/
|
|
940
985
|
export function getAddress(type, privKey, network = NETWORK) {
|
|
986
|
+
u.astring(type, 'type');
|
|
941
987
|
if (type === 'tr') {
|
|
942
988
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
943
989
|
}
|
|
@@ -1076,15 +1122,21 @@ export function WIF(network = NETWORK) {
|
|
|
1076
1122
|
* ```
|
|
1077
1123
|
*/
|
|
1078
1124
|
export function Address(network = NETWORK) {
|
|
1125
|
+
u.validateObject(network, {}, {}, 'network');
|
|
1079
1126
|
return {
|
|
1080
1127
|
encode(from) {
|
|
1128
|
+
u.validateObject(from, {}, {}, 'from');
|
|
1081
1129
|
const { type } = from;
|
|
1130
|
+
u.astring(type, 'from.type');
|
|
1082
1131
|
if (type === 'wpkh')
|
|
1083
1132
|
return programToWitness(0, from.hash, network);
|
|
1084
1133
|
else if (type === 'wsh')
|
|
1085
1134
|
return programToWitness(0, from.hash, network);
|
|
1086
1135
|
else if (type === 'tr')
|
|
1087
1136
|
return programToWitness(1, from.pubkey, network);
|
|
1137
|
+
// BIP433 P2A is the fixed v1 witness program 0x4e73 ('bc1pfeessrawgf').
|
|
1138
|
+
else if (type === 'p2a')
|
|
1139
|
+
return programToWitness(1, P2A_PROGRAM, network);
|
|
1088
1140
|
else if (type === 'pkh')
|
|
1089
1141
|
return formatKey(from.hash, [network.pubKeyHash]);
|
|
1090
1142
|
else if (type === 'sh')
|
|
@@ -1092,6 +1144,7 @@ export function Address(network = NETWORK) {
|
|
|
1092
1144
|
throw new Error(`Unknown address type=${type}`);
|
|
1093
1145
|
},
|
|
1094
1146
|
decode(address) {
|
|
1147
|
+
u.astring(address, 'address');
|
|
1095
1148
|
if (address.length < 14 || address.length > 74)
|
|
1096
1149
|
throw new Error('Invalid address length');
|
|
1097
1150
|
// Bech32
|
|
@@ -1119,8 +1172,10 @@ export function Address(network = NETWORK) {
|
|
|
1119
1172
|
return { type: 'wpkh', hash: data };
|
|
1120
1173
|
else if (version === 1 && data.length === 32)
|
|
1121
1174
|
return { type: 'tr', pubkey: data };
|
|
1175
|
+
else if (version === 1 && u.equalBytes(data, P2A_PROGRAM))
|
|
1176
|
+
return { type: 'p2a', script: Script.encode([1, data]) };
|
|
1122
1177
|
// Future witness versions can still be valid addresses, but this helper
|
|
1123
|
-
// only returns typed descriptors for recognized v0 and
|
|
1178
|
+
// only returns typed descriptors for recognized v0, taproot and P2A templates.
|
|
1124
1179
|
else
|
|
1125
1180
|
throw new Error('Unknown witness program');
|
|
1126
1181
|
}
|
|
@@ -1141,4 +1196,3 @@ export function Address(network = NETWORK) {
|
|
|
1141
1196
|
},
|
|
1142
1197
|
};
|
|
1143
1198
|
}
|
|
1144
|
-
//# sourceMappingURL=payment.js.map
|
package/psbt.d.ts
CHANGED
package/psbt.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { hex } from '@scure/base';
|
|
2
|
+
import { anumber } from '@noble/hashes/utils.js';
|
|
2
3
|
import * as P from 'micro-packed';
|
|
3
4
|
import { CompactSize, CompactSizeLen, RawOldTx, RawOutput, RawTx, RawWitness, VarBytes, } from "./script.js";
|
|
4
|
-
import { compareBytes, equalBytes, PubT, validatePubkey, } from "./utils.js";
|
|
5
|
+
import { aarray, compareBytes, equalBytes, PubT, validateObject, validatePubkey, } from "./utils.js";
|
|
5
6
|
// PSBT BIP174, BIP370, BIP371
|
|
7
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
8
|
+
// prettier-ignore
|
|
9
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
6
10
|
// BIP174 keydata only says "public key", so legacy PSBT ECDSA fields still accept both
|
|
7
11
|
// compressed (33-byte) and uncompressed (65-byte) SEC1 encodings, but not x-only keys.
|
|
8
12
|
const PubKeyECDSA = /* @__PURE__ */ (() => P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa)))();
|
|
@@ -121,10 +125,10 @@ const tapTree = /* @__PURE__ */ (() => P.validate(P.array(null, P.struct({
|
|
|
121
125
|
next.push(0);
|
|
122
126
|
path = next;
|
|
123
127
|
}
|
|
124
|
-
let leaves =
|
|
128
|
+
let leaves = _0n;
|
|
125
129
|
for (let i = 0; i < tree.length; i++)
|
|
126
|
-
leaves +=
|
|
127
|
-
if (leaves !==
|
|
130
|
+
leaves += _1n << BigInt(maxDepth - tree[i].depth);
|
|
131
|
+
if (leaves !== _1n << BigInt(maxDepth))
|
|
128
132
|
throw new Error('tapTree: tuples must describe a complete binary tree');
|
|
129
133
|
return tree;
|
|
130
134
|
}))();
|
|
@@ -486,7 +490,7 @@ export const PSBTOutputCoder = /* @__PURE__ */ (() => Object.freeze(P.validate(P
|
|
|
486
490
|
// in the field coder itself because BIP371 constrains the tuple value, not just the row shape.
|
|
487
491
|
// BIP174/BIP370 define PSBT_OUT_AMOUNT as a signed int64 transport field, but it still
|
|
488
492
|
// represents the transaction output amount in satoshis, so negative output values are invalid.
|
|
489
|
-
if (o.amount !== undefined && o.amount <
|
|
493
|
+
if (o.amount !== undefined && o.amount < _0n)
|
|
490
494
|
throw new Error(`validateOutput: wrong amount=${o.amount}`);
|
|
491
495
|
if (o.bip32Derivation)
|
|
492
496
|
for (const [k] of o.bip32Derivation)
|
|
@@ -574,6 +578,9 @@ function validatePSBTFields(version, info, lst) {
|
|
|
574
578
|
* ```
|
|
575
579
|
*/
|
|
576
580
|
export function cleanPSBTFields(version, info, lst) {
|
|
581
|
+
anumber(version, 'version');
|
|
582
|
+
validateObject(info, {}, {}, 'info');
|
|
583
|
+
validateObject(lst, {}, {}, 'lst');
|
|
577
584
|
const _lst = lst;
|
|
578
585
|
const out = {};
|
|
579
586
|
for (const _k in _lst) {
|
|
@@ -647,6 +654,12 @@ function validatePSBT(tx) {
|
|
|
647
654
|
* ```
|
|
648
655
|
*/
|
|
649
656
|
export function mergeKeyMap(psbtEnum, val, cur, allowedFields, allowUnknown) {
|
|
657
|
+
validateObject(psbtEnum, {}, {}, 'psbtEnum');
|
|
658
|
+
validateObject(val, {}, {}, 'val');
|
|
659
|
+
if (cur !== undefined)
|
|
660
|
+
validateObject(cur, {}, {}, 'cur');
|
|
661
|
+
if (allowedFields !== undefined)
|
|
662
|
+
aarray(allowedFields, 'allowedFields');
|
|
650
663
|
const _val = val;
|
|
651
664
|
const _cur = cur;
|
|
652
665
|
const _allowedFields = allowedFields;
|
|
@@ -750,4 +763,3 @@ export const RawPSBTV0 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSB
|
|
|
750
763
|
// This wrapper only layers `validatePSBT`'s PSBTv2 required-field/count reconciliation on top
|
|
751
764
|
// of `_RawPSBTV2`; nested input/output/global field invariants still depend on the coders below.
|
|
752
765
|
export const RawPSBTV2 = /* @__PURE__ */ (() => Object.freeze(P.validate(_RawPSBTV2, validatePSBT)))();
|
|
753
|
-
//# sourceMappingURL=psbt.js.map
|
package/script.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Bytes, type TArg, type TRet, type ValueOf } from './utils.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Maximum byte size allowed for a single pushed script element.
|
|
5
5
|
* BIP 342 keeps this 520-byte stack-element limit even though tapscript removes
|
|
@@ -351,4 +351,3 @@ export declare const RawOldTx: Readonly<P.CoderType<P.StructInput<{
|
|
|
351
351
|
lockTime: number;
|
|
352
352
|
}>>>;
|
|
353
353
|
export {};
|
|
354
|
-
//# sourceMappingURL=script.d.ts.map
|