@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/src/p2p.ts
CHANGED
|
@@ -42,6 +42,9 @@ const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
|
|
|
42
42
|
const _3n = BigInt(3);
|
|
43
43
|
const _4n = BigInt(4);
|
|
44
44
|
const _7n = BigInt(7);
|
|
45
|
+
// Precomputed 1/2 mod p: turns the frequent "divide by 2" steps of XSwiftEC/XSwiftECInv
|
|
46
|
+
// into single multiplications instead of one modular inversion per call.
|
|
47
|
+
const INV_2 = Fp.inv(Fp.create(_2n));
|
|
45
48
|
// This is the "lift_x(x) succeeds" predicate for field-normalized x values.
|
|
46
49
|
// Raw x >= p would need the full BIP340 range check before reducing modulo p.
|
|
47
50
|
const isValidX = (x: bigint) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
|
|
@@ -91,7 +94,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
91
94
|
const r = trySqrt(Fp.mul(Fp.neg(s), Fp.add(Fp.mul(_4n, t0), t1)));
|
|
92
95
|
if (r === undefined) return; // [2 condition]
|
|
93
96
|
if (ellCase & 1 && Fp.is0(r)) return;
|
|
94
|
-
v = Fp.
|
|
97
|
+
v = Fp.mul(Fp.add(Fp.neg(u), Fp.div(r, s)), INV_2); // v = (-u + r / s) / 2
|
|
95
98
|
}
|
|
96
99
|
const w = trySqrt(s);
|
|
97
100
|
if (w === undefined) return; // [3 condition]
|
|
@@ -99,7 +102,7 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
99
102
|
const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
|
|
100
103
|
const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
|
|
101
104
|
// w0 * (u * t0 / 2 + v)
|
|
102
|
-
return Fp.mul(w0, Fp.add(Fp.
|
|
105
|
+
return Fp.mul(w0, Fp.add(Fp.mul(Fp.mul(u, t0), INV_2), v));
|
|
103
106
|
},
|
|
104
107
|
// Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
|
|
105
108
|
// BIP324 samples encodings for x(P), so callers must pass a curve X coordinate in 0..p-1;
|
|
@@ -109,11 +112,18 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
109
112
|
// so encode() must reject out-of-range x instead of silently reducing a different bigint modulo p.
|
|
110
113
|
if (!Fp.isValid(x))
|
|
111
114
|
throw new RangeError('elligatorSwift.encode: expected x coordinate in range 0..p-1');
|
|
115
|
+
// Off-curve x cannot round-trip: decode() only returns lift_x-able candidates, so
|
|
116
|
+
// the loop below would silently emit an encoding of a *different* public key.
|
|
117
|
+
if (!isValidX(x))
|
|
118
|
+
throw new RangeError('elligatorSwift.encode: expected x coordinate of a curve point');
|
|
112
119
|
// 200k test cycles per keygen: avg=4 max=48
|
|
113
120
|
// seems too much, but same as for reference implementation
|
|
114
121
|
while (true) {
|
|
115
|
-
//
|
|
116
|
-
|
|
122
|
+
// Random field element 1..p-1: BIP324 samples u over the whole field (the previous
|
|
123
|
+
// secret-key sampler silently restricted u to 1..n-1); decode() remaps u = 0, so
|
|
124
|
+
// zero cannot round-trip and is skipped.
|
|
125
|
+
const u = Fp.create(Fp.fromBytes(randomBytes(32), true));
|
|
126
|
+
if (Fp.is0(u)) continue;
|
|
117
127
|
const ellCase = randomBytes(1)[0] & 7; // [0..8)
|
|
118
128
|
const t = elligatorSwift._inv(x, u, ellCase);
|
|
119
129
|
if (!t) continue;
|
|
@@ -140,9 +150,11 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
140
150
|
// try different cases
|
|
141
151
|
let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
|
|
142
152
|
if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
|
|
143
|
-
|
|
153
|
+
// X / Y is shared by the remaining candidates; computing it once saves an inversion.
|
|
154
|
+
const xDivY = Fp.div(x, y);
|
|
155
|
+
res = Fp.mul(Fp.sub(Fp.neg(xDivY), u), INV_2); // (-X / Y - u) / 2
|
|
144
156
|
if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
|
|
145
|
-
res = Fp.
|
|
157
|
+
res = Fp.mul(Fp.sub(xDivY, u), INV_2); // (X / Y - u) / 2
|
|
146
158
|
if (isValidX(res)) return Fp.toBytes(res) as TRet<Uint8Array>;
|
|
147
159
|
throw new Error('elligatorSwift: cannot decode public key');
|
|
148
160
|
},
|
|
@@ -173,6 +185,10 @@ export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
|
173
185
|
): TRet<Uint8Array> => {
|
|
174
186
|
// BIP324 Shared secret computation hashes "the exactly 64-byte public keys'
|
|
175
187
|
// encodings sent over the wire", so both ElligatorSwift inputs must be 64 bytes here.
|
|
188
|
+
// Initiator/responder ordering decides the hash-input order, so require a real boolean
|
|
189
|
+
// instead of letting arbitrary truthy values pick a side.
|
|
190
|
+
if (typeof initiating !== 'boolean')
|
|
191
|
+
throw new TypeError('"initiating" expected boolean, got type=' + typeof initiating);
|
|
176
192
|
const ours = abytes(publicKeyOurs, 64, 'publicKeyOurs');
|
|
177
193
|
const theirs = abytes(publicKeyTheirs, 64, 'publicKeyTheirs');
|
|
178
194
|
const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
|
package/src/payment.ts
CHANGED
|
@@ -29,11 +29,18 @@ export type P2Ret = {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
// Pay to Anchor (P2A)
|
|
32
|
+
// BIP433 Pay-to-Anchor witness program bytes; the scriptPubKey is `OP_1 <0x4e73>`.
|
|
33
|
+
const P2A_PROGRAM = /* @__PURE__ */ Uint8Array.from([0x4e, 0x73]);
|
|
32
34
|
type OutP2AType = { type: 'p2a'; script: Bytes };
|
|
33
35
|
const OutP2A: Coder<OptScript, OutP2AType | undefined> = {
|
|
34
36
|
encode(from: TArg<ScriptType>): TRet<OutP2AType | undefined> {
|
|
35
37
|
// BIP433 defines P2A as the exact OP_1 <0x4e73> scriptPubKey.
|
|
36
|
-
if (
|
|
38
|
+
if (
|
|
39
|
+
from.length !== 2 ||
|
|
40
|
+
from[0] !== 1 ||
|
|
41
|
+
!u.isBytes(from[1]) ||
|
|
42
|
+
!u.equalBytes(from[1], P2A_PROGRAM)
|
|
43
|
+
)
|
|
37
44
|
return;
|
|
38
45
|
return { type: 'p2a', script: Script.encode(from) } as TRet<OutP2AType | undefined>;
|
|
39
46
|
},
|
|
@@ -41,7 +48,7 @@ const OutP2A: Coder<OptScript, OutP2AType | undefined> = {
|
|
|
41
48
|
if (to.type !== 'p2a') return;
|
|
42
49
|
// The decoded object keeps `script` for caller convenience, but the `p2a`
|
|
43
50
|
// tag always canonicalizes back to the fixed BIP433 script.
|
|
44
|
-
return [1,
|
|
51
|
+
return [1, Uint8Array.from(P2A_PROGRAM)] as TRet<OptScript>;
|
|
45
52
|
},
|
|
46
53
|
};
|
|
47
54
|
|
|
@@ -87,8 +94,9 @@ const OutPKH: Coder<OptScript, OutPKHType | undefined> = {
|
|
|
87
94
|
encode(from: TArg<ScriptType>): TRet<OutPKHType | undefined> {
|
|
88
95
|
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !u.isBytes(from[2]))
|
|
89
96
|
return;
|
|
90
|
-
//
|
|
91
|
-
//
|
|
97
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
98
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
99
|
+
if (from[2].length !== 20) return;
|
|
92
100
|
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG') return;
|
|
93
101
|
return { type: 'pkh', hash: from[2] } as TRet<OutPKHType | undefined>;
|
|
94
102
|
},
|
|
@@ -105,8 +113,9 @@ const OutSH: Coder<OptScript, OutSHType | undefined> = {
|
|
|
105
113
|
encode(from: TArg<ScriptType>): TRet<OutSHType | undefined> {
|
|
106
114
|
if (from.length !== 3 || from[0] !== 'HASH160' || !u.isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
107
115
|
return;
|
|
108
|
-
//
|
|
109
|
-
//
|
|
116
|
+
// Require the exact 20-byte HASH160 here so near-miss scripts fall through
|
|
117
|
+
// to OutUnknown instead of throwing in the OutScript validator on decode.
|
|
118
|
+
if (from[1].length !== 20) return;
|
|
110
119
|
return { type: 'sh', hash: from[1] } as TRet<OutSHType | undefined>;
|
|
111
120
|
},
|
|
112
121
|
// OutScript validates `sh.hash` before this branch emits the canonical
|
|
@@ -159,9 +168,12 @@ const OutMS: Coder<OptScript, OutMSType | undefined> = {
|
|
|
159
168
|
if (typeof m !== 'number' || typeof n !== 'number') return;
|
|
160
169
|
const pubkeys = from.slice(1, -2);
|
|
161
170
|
if (n !== pubkeys.length) return;
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
//
|
|
171
|
+
// Require valid ECDSA pubkeys and `0 < m <= n` here so near-miss
|
|
172
|
+
// CHECKMULTISIG scripts (garbage keys, degenerate 0-of-0) fall through to
|
|
173
|
+
// OutUnknown instead of throwing in the OutScript validator on decode.
|
|
174
|
+
// Script.decode only yields 0..16 for opcode numbers, so n <= 16 holds.
|
|
175
|
+
for (const pub of pubkeys) if (!u.isBytes(pub) || !isValidPubkey(pub, u.PubT.ecdsa)) return;
|
|
176
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > n) return;
|
|
165
177
|
// We don't need n here because it is the same as pubkeys.length.
|
|
166
178
|
return { type: 'ms', m, pubkeys: pubkeys as Bytes[] } as TRet<OutMSType | undefined>;
|
|
167
179
|
},
|
|
@@ -182,6 +194,10 @@ const OutTR: Coder<OptScript, OutTRType | undefined> = {
|
|
|
182
194
|
// BIP341 assigns native taproot meaning only to version 1 with a 32-byte x-only program;
|
|
183
195
|
// other OP_1 program lengths remain reserved future witness programs and should fall through.
|
|
184
196
|
if (from.length !== 2 || from[0] !== 1 || !u.isBytes(from[1]) || from[1].length !== 32) return;
|
|
197
|
+
// A 32-byte v1 program with an off-curve x coordinate is a fundable but
|
|
198
|
+
// taproot-unspendable output; classify it as unknown instead of throwing
|
|
199
|
+
// in the OutScript validator on decode.
|
|
200
|
+
if (!isValidPubkey(from[1], u.PubT.schnorr)) return;
|
|
185
201
|
return { type: 'tr', pubkey: from[1] } as TRet<OutTRType | undefined>;
|
|
186
202
|
},
|
|
187
203
|
// OutScript validates `tr.pubkey` before this branch emits the canonical
|
|
@@ -243,9 +259,13 @@ const OutTRMS: Coder<OptScript, OutTRMSType | undefined> = {
|
|
|
243
259
|
if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD')) return;
|
|
244
260
|
continue;
|
|
245
261
|
}
|
|
246
|
-
|
|
262
|
+
// Require actual Schnorr pubkeys here (same as tr_ns) so near-miss
|
|
263
|
+
// CHECKSIGADD scripts fall through to OutUnknown instead of throwing
|
|
264
|
+
// in the OutScript validator on decode.
|
|
265
|
+
if (!u.isBytes(elm) || !isValidPubkey(elm, u.PubT.schnorr)) return;
|
|
247
266
|
pubkeys.push(elm);
|
|
248
267
|
}
|
|
268
|
+
if (!Number.isSafeInteger(m) || m < 1 || m > pubkeys.length || pubkeys.length > 999) return;
|
|
249
269
|
return { type: 'tr_ms', pubkeys, m } as TRet<OutTRMSType | undefined>;
|
|
250
270
|
},
|
|
251
271
|
decode: (to: TArg<OutTRMSType>): TRet<OptScript> => {
|
|
@@ -408,7 +428,8 @@ export const OutScript: TRet<
|
|
|
408
428
|
export type OutScriptType = typeof OutScript;
|
|
409
429
|
// TRet-wrapping OutScript changes decode() to the normalized descriptor surface, but the local
|
|
410
430
|
// checkScript/Address caches still need an explicit alias that can carry the decode-side `undefined`.
|
|
411
|
-
type
|
|
431
|
+
type AddressValue = NonNullable<ReturnType<OutScriptType['decode']>>;
|
|
432
|
+
type OutScriptValue = AddressValue | undefined;
|
|
412
433
|
|
|
413
434
|
// Basic sanity check for scripts
|
|
414
435
|
function checkWSH(s: TArg<OutWSHType>, witnessScript: TArg<Bytes>) {
|
|
@@ -606,6 +627,7 @@ export const p2sh = <T extends P2Ret>(
|
|
|
606
627
|
child: TArg<T>,
|
|
607
628
|
network: BTC_NETWORK = NETWORK
|
|
608
629
|
): TRet<Extends<P2SHReturn<T>, P2Ret>> => {
|
|
630
|
+
u.validateObject(child as Record<string, any>, {}, {}, 'child');
|
|
609
631
|
// It is already tested inside noble-hashes and checkScript
|
|
610
632
|
// BIP16 redeemScripts are pushed by scriptSig, so anything over the 520-byte pushed-element
|
|
611
633
|
// limit would be fundable by HASH160 but unspendable once wrapped in P2SH.
|
|
@@ -672,6 +694,7 @@ export const p2wsh = (
|
|
|
672
694
|
child: TArg<P2Ret>,
|
|
673
695
|
network: BTC_NETWORK = NETWORK
|
|
674
696
|
): TRet<Extends<P2WSH, P2Ret>> => {
|
|
697
|
+
u.validateObject(child as Record<string, any>, {}, {}, 'child');
|
|
675
698
|
const cs = child.script;
|
|
676
699
|
if (!u.isBytes(cs)) throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
677
700
|
// BIP141 P2WSH says the witness "must consist of ... a serialized script (witnessScript)"
|
|
@@ -782,9 +805,16 @@ function checkTaprootScript(
|
|
|
782
805
|
// disable custom. All custom scripts for taproot should have prefix 'tr_'
|
|
783
806
|
if (customScripts) {
|
|
784
807
|
const cs = P.apply(Script, P.coders.match(customScripts));
|
|
785
|
-
|
|
808
|
+
let c;
|
|
809
|
+
// match() throws when no custom coder matches; treat that as "not a custom
|
|
810
|
+
// script" so the allowUnknownOutputs escape below stays reachable.
|
|
811
|
+
try {
|
|
812
|
+
c = cs.decode(script);
|
|
813
|
+
} catch (e) {
|
|
814
|
+
c = undefined;
|
|
815
|
+
}
|
|
786
816
|
if (c !== undefined) {
|
|
787
|
-
if (
|
|
817
|
+
if (!u.astring(c.type, 'c.type').startsWith('tr_'))
|
|
788
818
|
throw new Error(`P2TR: invalid custom type=${c.type}`);
|
|
789
819
|
return;
|
|
790
820
|
}
|
|
@@ -867,6 +897,13 @@ type _TaprootTreeInternal = {
|
|
|
867
897
|
* ```
|
|
868
898
|
*/
|
|
869
899
|
export function taprootListToTree(taprootList: TArg<TaprootScriptList>): TRet<TaprootScriptTree> {
|
|
900
|
+
u.aarray<TaprootScriptList[number]>(taprootList, 'taprootList', (leaf, title) => {
|
|
901
|
+
// p2tr reduces non-binary trees through this helper, so nested branch arrays are valid here.
|
|
902
|
+
if (Array.isArray(leaf)) return;
|
|
903
|
+
u.validateObject(leaf as Record<string, any>, {}, {}, title);
|
|
904
|
+
// This helper only arranges weighted tree nodes; p2tr validates leaf scripts while hashing.
|
|
905
|
+
if (leaf.weight !== undefined) anumber(leaf.weight, title + '.weight');
|
|
906
|
+
});
|
|
870
907
|
// Empty flat lists cannot represent a taproot script tree; omit the tree entirely for
|
|
871
908
|
// key-path-only outputs instead of passing [] here, otherwise this helper would return
|
|
872
909
|
// undefined and downstream taproot tree walkers would fail much later on a non-tree value.
|
|
@@ -947,17 +984,22 @@ function taprootHashTree(
|
|
|
947
984
|
allowUnknownOutputs = false,
|
|
948
985
|
customScripts?: TArg<CustomScript[]>
|
|
949
986
|
): TRet<HashedTree> {
|
|
950
|
-
if (
|
|
987
|
+
if (tree === undefined) throw new Error('taprootHashTree: empty tree');
|
|
988
|
+
if (!Array.isArray(tree) && !P.utils.isPlainObject(tree))
|
|
989
|
+
throw new TypeError('"tree" expected object or array, got type=' + typeof tree);
|
|
951
990
|
if (Array.isArray(tree) && tree.length === 1) tree = tree[0];
|
|
952
991
|
// Terminal node (leaf)
|
|
953
992
|
if (!Array.isArray(tree)) {
|
|
993
|
+
u.validateObject(tree as Record<string, any>, {}, {}, 'tree');
|
|
954
994
|
const version = tree.leafVersion;
|
|
955
995
|
const { script: leafScript } = tree;
|
|
956
996
|
// Earliest tree walk where we can validate tapScripts
|
|
957
997
|
if (tree.tapLeafScript || (tree.tapMerkleRoot && !u.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
958
998
|
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
959
|
-
const script =
|
|
960
|
-
|
|
999
|
+
const script =
|
|
1000
|
+
typeof leafScript === 'string'
|
|
1001
|
+
? hex.decode(leafScript)
|
|
1002
|
+
: abytes(leafScript, undefined, 'tree.script');
|
|
961
1003
|
checkTaprootScript(script, internalPubKey, allowUnknownOutputs, customScripts);
|
|
962
1004
|
return {
|
|
963
1005
|
type: 'leaf',
|
|
@@ -1071,18 +1113,20 @@ export function p2tr(
|
|
|
1071
1113
|
);
|
|
1072
1114
|
const tapMerkleRoot = hashedTree.hash;
|
|
1073
1115
|
const [tweakedPubkey, parity] = u.taprootTweakPubkey(pubKey, tapMerkleRoot);
|
|
1116
|
+
const tapLeafScript: NonNullable<TransactionInput['tapLeafScript']> = [];
|
|
1074
1117
|
const leaves = taprootWalkTree(hashedTree).map((l) => {
|
|
1075
1118
|
const version = tapLeafVersion(l.version);
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
internalKey: pubKey,
|
|
1083
|
-
merklePath: l.path,
|
|
1084
|
-
}),
|
|
1119
|
+
// Leaf versions are stored as the base even byte; only the control block adds the
|
|
1120
|
+
// output-key parity bit required by BIP 341 script-path spending.
|
|
1121
|
+
const controlBlock = {
|
|
1122
|
+
version: version + parity,
|
|
1123
|
+
internalKey: pubKey,
|
|
1124
|
+
merklePath: l.path,
|
|
1085
1125
|
};
|
|
1126
|
+
// Skip an encode/decode copy for performance; callers must treat returned metadata as
|
|
1127
|
+
// immutable.
|
|
1128
|
+
tapLeafScript.push([controlBlock, u.concatBytes(l.script, new Uint8Array([version]))]);
|
|
1129
|
+
return { ...l, controlBlock: TaprootControlBlock.encode(controlBlock) };
|
|
1086
1130
|
});
|
|
1087
1131
|
return {
|
|
1088
1132
|
type: 'tr',
|
|
@@ -1093,10 +1137,7 @@ export function p2tr(
|
|
|
1093
1137
|
// PSBT stuff
|
|
1094
1138
|
tapInternalKey: pubKey,
|
|
1095
1139
|
leaves,
|
|
1096
|
-
tapLeafScript
|
|
1097
|
-
TaprootControlBlock.decode(l.controlBlock),
|
|
1098
|
-
u.concatBytes(l.script, new Uint8Array([tapLeafVersion(l.version)])),
|
|
1099
|
-
]),
|
|
1140
|
+
tapLeafScript,
|
|
1100
1141
|
tapMerkleRoot,
|
|
1101
1142
|
} as const as TRet<Extends<P2TR_TREE, P2Ret>>;
|
|
1102
1143
|
} else {
|
|
@@ -1278,6 +1319,7 @@ export function getAddress(
|
|
|
1278
1319
|
privKey: TArg<Bytes>,
|
|
1279
1320
|
network: BTC_NETWORK = NETWORK
|
|
1280
1321
|
): string {
|
|
1322
|
+
u.astring(type, 'type');
|
|
1281
1323
|
if (type === 'tr') {
|
|
1282
1324
|
return p2tr(u.pubSchnorr(privKey), undefined, network).address;
|
|
1283
1325
|
}
|
|
@@ -1429,18 +1471,24 @@ export function WIF(network: BTC_NETWORK = NETWORK): TRet<Coder<Bytes, string>>
|
|
|
1429
1471
|
* coder.encode(p2wpkh(pubECDSA(randomPrivateKeyBytes())));
|
|
1430
1472
|
* ```
|
|
1431
1473
|
*/
|
|
1432
|
-
export function Address(network: BTC_NETWORK = NETWORK) {
|
|
1474
|
+
export function Address(network: BTC_NETWORK = NETWORK): TRet<P.Coder<AddressValue, string>> {
|
|
1475
|
+
u.validateObject(network as Record<string, any>, {}, {}, 'network');
|
|
1433
1476
|
return {
|
|
1434
|
-
encode(from:
|
|
1477
|
+
encode(from: TArg<AddressValue>): string {
|
|
1478
|
+
u.validateObject(from as Record<string, any>, {}, {}, 'from');
|
|
1435
1479
|
const { type } = from;
|
|
1480
|
+
u.astring(type, 'from.type');
|
|
1436
1481
|
if (type === 'wpkh') return programToWitness(0, from.hash, network);
|
|
1437
1482
|
else if (type === 'wsh') return programToWitness(0, from.hash, network);
|
|
1438
1483
|
else if (type === 'tr') return programToWitness(1, from.pubkey, network);
|
|
1484
|
+
// BIP433 P2A is the fixed v1 witness program 0x4e73 ('bc1pfeessrawgf').
|
|
1485
|
+
else if (type === 'p2a') return programToWitness(1, P2A_PROGRAM, network);
|
|
1439
1486
|
else if (type === 'pkh') return formatKey(from.hash, [network.pubKeyHash]);
|
|
1440
1487
|
else if (type === 'sh') return formatKey(from.hash, [network.scriptHash]);
|
|
1441
1488
|
throw new Error(`Unknown address type=${type}`);
|
|
1442
1489
|
},
|
|
1443
|
-
decode(address: string):
|
|
1490
|
+
decode(address: string): TRet<AddressValue> {
|
|
1491
|
+
u.astring(address, 'address');
|
|
1444
1492
|
if (address.length < 14 || address.length > 74) throw new Error('Invalid address length');
|
|
1445
1493
|
// Bech32
|
|
1446
1494
|
if (network.bech32 && address.toLowerCase().startsWith(`${network.bech32}1`)) {
|
|
@@ -1458,25 +1506,27 @@ export function Address(network: BTC_NETWORK = NETWORK) {
|
|
|
1458
1506
|
const data = bech32.fromWords(program);
|
|
1459
1507
|
validateWitness(version, data);
|
|
1460
1508
|
if (version === 0 && data.length === 32)
|
|
1461
|
-
return { type: 'wsh', hash: data } as
|
|
1509
|
+
return { type: 'wsh', hash: data } as TRet<AddressValue>;
|
|
1462
1510
|
else if (version === 0 && data.length === 20)
|
|
1463
|
-
return { type: 'wpkh', hash: data } as
|
|
1511
|
+
return { type: 'wpkh', hash: data } as TRet<AddressValue>;
|
|
1464
1512
|
else if (version === 1 && data.length === 32)
|
|
1465
|
-
return { type: 'tr', pubkey: data } as
|
|
1513
|
+
return { type: 'tr', pubkey: data } as TRet<AddressValue>;
|
|
1514
|
+
else if (version === 1 && u.equalBytes(data, P2A_PROGRAM))
|
|
1515
|
+
return { type: 'p2a', script: Script.encode([1, data]) } as TRet<AddressValue>;
|
|
1466
1516
|
// Future witness versions can still be valid addresses, but this helper
|
|
1467
|
-
// only returns typed descriptors for recognized v0 and
|
|
1517
|
+
// only returns typed descriptors for recognized v0, taproot and P2A templates.
|
|
1468
1518
|
else throw new Error('Unknown witness program');
|
|
1469
1519
|
}
|
|
1470
1520
|
const data = base58check.decode(address);
|
|
1471
1521
|
if (data.length !== 21) throw new Error('Invalid base58 address');
|
|
1472
1522
|
// Pay To Public Key Hash
|
|
1473
1523
|
if (data[0] === network.pubKeyHash) {
|
|
1474
|
-
return { type: 'pkh', hash: data.slice(1) } as
|
|
1524
|
+
return { type: 'pkh', hash: data.slice(1) } as TRet<AddressValue>;
|
|
1475
1525
|
} else if (data[0] === network.scriptHash) {
|
|
1476
1526
|
return {
|
|
1477
1527
|
type: 'sh',
|
|
1478
1528
|
hash: data.slice(1),
|
|
1479
|
-
} as
|
|
1529
|
+
} as TRet<AddressValue>;
|
|
1480
1530
|
}
|
|
1481
1531
|
throw new Error(`Invalid address prefix=${data[0]}`);
|
|
1482
1532
|
},
|
package/src/psbt.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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 {
|
|
4
5
|
CompactSize,
|
|
@@ -10,17 +11,23 @@ import {
|
|
|
10
11
|
VarBytes,
|
|
11
12
|
} from './script.ts';
|
|
12
13
|
import {
|
|
14
|
+
aarray,
|
|
13
15
|
type Bytes,
|
|
14
16
|
compareBytes,
|
|
15
17
|
equalBytes,
|
|
16
18
|
PubT,
|
|
17
19
|
type TArg,
|
|
18
20
|
type TRet,
|
|
21
|
+
validateObject,
|
|
19
22
|
validatePubkey,
|
|
20
23
|
} from './utils.ts';
|
|
21
24
|
|
|
22
25
|
// PSBT BIP174, BIP370, BIP371
|
|
23
26
|
|
|
27
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
28
|
+
// prettier-ignore
|
|
29
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
30
|
+
|
|
24
31
|
// BIP174 keydata only says "public key", so legacy PSBT ECDSA fields still accept both
|
|
25
32
|
// compressed (33-byte) and uncompressed (65-byte) SEC1 encodings, but not x-only keys.
|
|
26
33
|
const PubKeyECDSA: P.CoderType<Bytes> = /* @__PURE__ */ (() =>
|
|
@@ -157,9 +164,9 @@ const tapTree = /* @__PURE__ */ (() =>
|
|
|
157
164
|
while (next.length < depth) next.push(0);
|
|
158
165
|
path = next;
|
|
159
166
|
}
|
|
160
|
-
let leaves =
|
|
161
|
-
for (let i = 0; i < tree.length; i++) leaves +=
|
|
162
|
-
if (leaves !==
|
|
167
|
+
let leaves = _0n;
|
|
168
|
+
for (let i = 0; i < tree.length; i++) leaves += _1n << BigInt(maxDepth - tree[i].depth);
|
|
169
|
+
if (leaves !== _1n << BigInt(maxDepth))
|
|
163
170
|
throw new Error('tapTree: tuples must describe a complete binary tree');
|
|
164
171
|
return tree;
|
|
165
172
|
}
|
|
@@ -593,7 +600,7 @@ export const PSBTOutputCoder = /* @__PURE__ */ (() =>
|
|
|
593
600
|
// in the field coder itself because BIP371 constrains the tuple value, not just the row shape.
|
|
594
601
|
// BIP174/BIP370 define PSBT_OUT_AMOUNT as a signed int64 transport field, but it still
|
|
595
602
|
// represents the transaction output amount in satoshis, so negative output values are invalid.
|
|
596
|
-
if (o.amount !== undefined && o.amount <
|
|
603
|
+
if (o.amount !== undefined && o.amount < _0n)
|
|
597
604
|
throw new Error(`validateOutput: wrong amount=${o.amount}`);
|
|
598
605
|
if (o.bip32Derivation) for (const [k] of o.bip32Derivation) validatePubkey(k, PubT.ecdsa);
|
|
599
606
|
return o;
|
|
@@ -726,6 +733,9 @@ export function cleanPSBTFields<T extends PSBTKeyMap>(
|
|
|
726
733
|
info: T,
|
|
727
734
|
lst: TArg<PSBTKeyMapKeys<T>>
|
|
728
735
|
): TRet<PSBTKeyMapKeys<T>> {
|
|
736
|
+
anumber(version, 'version');
|
|
737
|
+
validateObject(info as Record<string, any>, {}, {}, 'info');
|
|
738
|
+
validateObject(lst as Record<string, any>, {}, {}, 'lst');
|
|
729
739
|
const _lst = lst as PSBTKeyMapKeys<T>;
|
|
730
740
|
const out: PSBTKeyMapKeys<T> = {};
|
|
731
741
|
for (const _k in _lst) {
|
|
@@ -747,7 +757,7 @@ export function cleanPSBTFields<T extends PSBTKeyMap>(
|
|
|
747
757
|
return out as TRet<PSBTKeyMapKeys<T>>;
|
|
748
758
|
}
|
|
749
759
|
|
|
750
|
-
function validatePSBT(tx: P.UnwrapCoder<PSBTRaw>) {
|
|
760
|
+
function validatePSBT(tx: P.UnwrapCoder<PSBTRaw>): P.UnwrapCoder<PSBTRaw> {
|
|
751
761
|
const version = (tx && tx.global && tx.global.version) || 0;
|
|
752
762
|
validatePSBTFields(version, PSBTGlobal, tx.global);
|
|
753
763
|
for (const i of tx.inputs) validatePSBTFields(version, PSBTInput, i);
|
|
@@ -803,6 +813,10 @@ export function mergeKeyMap<T extends PSBTKeyMap>(
|
|
|
803
813
|
allowedFields?: TArg<readonly (keyof PSBTKeyMapKeys<T>)[]>,
|
|
804
814
|
allowUnknown?: boolean
|
|
805
815
|
): TRet<PSBTKeyMapKeys<T>> {
|
|
816
|
+
validateObject(psbtEnum as Record<string, any>, {}, {}, 'psbtEnum');
|
|
817
|
+
validateObject(val as Record<string, any>, {}, {}, 'val');
|
|
818
|
+
if (cur !== undefined) validateObject(cur as Record<string, any>, {}, {}, 'cur');
|
|
819
|
+
if (allowedFields !== undefined) aarray(allowedFields, 'allowedFields');
|
|
806
820
|
const _val = val as PSBTKeyMapKeys<T>;
|
|
807
821
|
const _cur = cur as PSBTKeyMapKeys<T> | undefined;
|
|
808
822
|
const _allowedFields = allowedFields as readonly (keyof PSBTKeyMapKeys<T>)[] | undefined;
|
package/src/script.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
aarray,
|
|
4
|
+
abytes,
|
|
5
|
+
isBytes,
|
|
6
|
+
reverseObject,
|
|
7
|
+
type Bytes,
|
|
8
|
+
type TArg,
|
|
9
|
+
type TRet,
|
|
10
|
+
type ValueOf,
|
|
11
|
+
} from './utils.ts';
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* Maximum byte size allowed for a single pushed script element.
|
|
@@ -8,6 +17,12 @@ import { isBytes, reverseObject, type ValueOf, type Bytes, type TArg, type TRet
|
|
|
8
17
|
*/
|
|
9
18
|
export const MAX_SCRIPT_BYTE_LENGTH = 520;
|
|
10
19
|
|
|
20
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
21
|
+
// prettier-ignore
|
|
22
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1), _2n = /* @__PURE__ */ BigInt(2), _8n = /* @__PURE__ */ BigInt(8);
|
|
23
|
+
const U8_MAX = /* @__PURE__ */ BigInt(0xff);
|
|
24
|
+
const COMPACT_DIRECT_MAX = /* @__PURE__ */ BigInt(0xfc);
|
|
25
|
+
|
|
11
26
|
// prettier-ignore
|
|
12
27
|
/**
|
|
13
28
|
* Bitcoin Script opcode table.
|
|
@@ -87,11 +102,11 @@ export type ScriptType = ScriptOP[];
|
|
|
87
102
|
export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<bigint> {
|
|
88
103
|
return P.wrap({
|
|
89
104
|
encodeStream: (w: P.Writer, value: bigint) => {
|
|
90
|
-
if (value ===
|
|
105
|
+
if (value === _0n) return;
|
|
91
106
|
const neg = value < 0;
|
|
92
107
|
const val = BigInt(value);
|
|
93
108
|
const nums = [];
|
|
94
|
-
for (let abs = neg ? -val : val; abs; abs >>=
|
|
109
|
+
for (let abs = neg ? -val : val; abs; abs >>= _8n) nums.push(Number(abs & U8_MAX));
|
|
95
110
|
if (nums[nums.length - 1] >= 0x80) nums.push(neg ? 0x80 : 0);
|
|
96
111
|
else if (neg) nums[nums.length - 1] |= 0x80;
|
|
97
112
|
w.bytes(new Uint8Array(nums));
|
|
@@ -100,24 +115,22 @@ export function ScriptNum(bytesLimit = 6, forceMinimal = false): P.CoderType<big
|
|
|
100
115
|
const len = r.leftBytes;
|
|
101
116
|
if (len > bytesLimit)
|
|
102
117
|
throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
|
|
103
|
-
if (len === 0) return
|
|
118
|
+
if (len === 0) return _0n;
|
|
119
|
+
// Read the payload once instead of peeking for the minimality check and
|
|
120
|
+
// then re-reading it byte-by-byte through the Reader.
|
|
121
|
+
const data = r.bytes(len);
|
|
104
122
|
if (forceMinimal) {
|
|
105
|
-
const data = r.bytes(len, true);
|
|
106
123
|
// MSB is zero (without sign bit) -> not minimally encoded
|
|
107
|
-
if ((data[
|
|
124
|
+
if ((data[len - 1] & 0x7f) === 0) {
|
|
108
125
|
// exception
|
|
109
|
-
if (len <= 1 || (data[
|
|
126
|
+
if (len <= 1 || (data[len - 2] & 0x80) === 0)
|
|
110
127
|
throw new Error('Non-minimally encoded ScriptNum');
|
|
111
128
|
}
|
|
112
129
|
}
|
|
113
|
-
let
|
|
114
|
-
let
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
res |= BigInt(last) << (8n * BigInt(i));
|
|
118
|
-
}
|
|
119
|
-
if (last >= 0x80) {
|
|
120
|
-
res &= (2n ** BigInt(len * 8) - 1n) >> 1n;
|
|
130
|
+
let res = _0n;
|
|
131
|
+
for (let i = 0; i < len; ++i) res |= BigInt(data[i]) << (_8n * BigInt(i));
|
|
132
|
+
if (data[len - 1] >= 0x80) {
|
|
133
|
+
res &= (_2n ** BigInt(len * 8) - _1n) >> _1n;
|
|
121
134
|
res = -res;
|
|
122
135
|
}
|
|
123
136
|
return res;
|
|
@@ -148,7 +161,9 @@ export function OpToNum(
|
|
|
148
161
|
if (isBytes(op)) {
|
|
149
162
|
try {
|
|
150
163
|
const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
|
|
151
|
-
|
|
164
|
+
// Symmetric safe-integer bound: large negative values would otherwise
|
|
165
|
+
// coerce through Number() with silent precision loss.
|
|
166
|
+
if (val > Number.MAX_SAFE_INTEGER || val < -Number.MAX_SAFE_INTEGER) return;
|
|
152
167
|
return Number(val);
|
|
153
168
|
} catch (e) {
|
|
154
169
|
return;
|
|
@@ -203,10 +218,14 @@ export const Script: TRet<P.CoderType<ScriptType>> = /* @__PURE__ */ (() =>
|
|
|
203
218
|
Object.freeze(
|
|
204
219
|
P.wrap({
|
|
205
220
|
encodeStream: (w: P.Writer, value: TArg<ScriptType>) => {
|
|
221
|
+
aarray(value, 'value');
|
|
206
222
|
for (let o of value) {
|
|
207
223
|
if (typeof o === 'string') {
|
|
208
|
-
|
|
209
|
-
|
|
224
|
+
const op = OP[o];
|
|
225
|
+
// OP is a plain object, so inherited Object.prototype keys ('toString',
|
|
226
|
+
// 'constructor', ...) are not opcodes and must be rejected here too.
|
|
227
|
+
if (typeof op !== 'number') throw new Error(`Unknown opcode=${o}`);
|
|
228
|
+
w.byte(op);
|
|
210
229
|
continue;
|
|
211
230
|
} else if (typeof o === 'number') {
|
|
212
231
|
if (o === 0x00) {
|
|
@@ -224,7 +243,7 @@ export const Script: TRet<P.CoderType<ScriptType>> = /* @__PURE__ */ (() =>
|
|
|
224
243
|
}
|
|
225
244
|
// Encode big numbers
|
|
226
245
|
if (typeof o === 'number') o = ScriptNum().encode(BigInt(o));
|
|
227
|
-
|
|
246
|
+
abytes(o, undefined, 'value');
|
|
228
247
|
// Bytes
|
|
229
248
|
const len = o.length;
|
|
230
249
|
if (len < OP.PUSHDATA1) w.byte(len);
|
|
@@ -267,13 +286,6 @@ export const Script: TRet<P.CoderType<ScriptType>> = /* @__PURE__ */ (() =>
|
|
|
267
286
|
})
|
|
268
287
|
))() as TRet<P.CoderType<ScriptType>>;
|
|
269
288
|
|
|
270
|
-
// BTC specific variable length integer encoding
|
|
271
|
-
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
272
|
-
const CSLimits: Record<number, [number, number, bigint, bigint]> = {
|
|
273
|
-
0xfd: [0xfd, 2, 253n, 65535n],
|
|
274
|
-
0xfe: [0xfe, 4, 65536n, 4294967295n],
|
|
275
|
-
0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
|
|
276
|
-
};
|
|
277
289
|
/**
|
|
278
290
|
* Bitcoin CompactSize integer coder.
|
|
279
291
|
* @example
|
|
@@ -282,16 +294,25 @@ const CSLimits: Record<number, [number, number, bigint, bigint]> = {
|
|
|
282
294
|
* CompactSize.encode(1n);
|
|
283
295
|
* ```
|
|
284
296
|
*/
|
|
285
|
-
export const CompactSize: P.CoderType<bigint> = /* @__PURE__ */ (() =>
|
|
286
|
-
|
|
297
|
+
export const CompactSize: P.CoderType<bigint> = /* @__PURE__ */ (() => {
|
|
298
|
+
// BTC specific variable length integer encoding
|
|
299
|
+
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
300
|
+
const limits: Record<number, [number, number, bigint, bigint]> = {
|
|
301
|
+
0xfd: [0xfd, 2, BigInt(0xfd), BigInt(0xffff)],
|
|
302
|
+
0xfe: [0xfe, 4, BigInt(0x10000), BigInt(0xffffffff)],
|
|
303
|
+
0xff: [0xff, 8, BigInt(0x100000000), BigInt('0xffffffffffffffff')],
|
|
304
|
+
};
|
|
305
|
+
// Hoisted: Object.values() would otherwise allocate a fresh array on every encode.
|
|
306
|
+
const limitsList = Object.values(limits);
|
|
307
|
+
return Object.freeze(
|
|
287
308
|
P.wrap({
|
|
288
309
|
encodeStream: (w: P.Writer, value: bigint) => {
|
|
289
310
|
if (typeof value === 'number') value = BigInt(value);
|
|
290
|
-
if (
|
|
291
|
-
for (const [flag, bytes, start, stop] of
|
|
311
|
+
if (_0n <= value && value <= COMPACT_DIRECT_MAX) return w.byte(Number(value));
|
|
312
|
+
for (const [flag, bytes, start, stop] of limitsList) {
|
|
292
313
|
if (start > value || value > stop) continue;
|
|
293
314
|
w.byte(flag);
|
|
294
|
-
for (let i = 0; i < bytes; i++) w.byte(Number((value >> (
|
|
315
|
+
for (let i = 0; i < bytes; i++) w.byte(Number((value >> (_8n * BigInt(i))) & U8_MAX));
|
|
295
316
|
return;
|
|
296
317
|
}
|
|
297
318
|
throw w.err(`VarInt too big: ${value}`);
|
|
@@ -299,16 +320,17 @@ export const CompactSize: P.CoderType<bigint> = /* @__PURE__ */ (() =>
|
|
|
299
320
|
decodeStream: (r: P.Reader): bigint => {
|
|
300
321
|
const b0 = r.byte();
|
|
301
322
|
if (b0 <= 0xfc) return BigInt(b0);
|
|
302
|
-
const [_, bytes, start] =
|
|
303
|
-
let num =
|
|
304
|
-
for (let i = 0; i < bytes; i++) num |= BigInt(r.byte()) << (
|
|
323
|
+
const [_, bytes, start] = limits[b0];
|
|
324
|
+
let num = _0n;
|
|
325
|
+
for (let i = 0; i < bytes; i++) num |= BigInt(r.byte()) << (_8n * BigInt(i));
|
|
305
326
|
// BIP 152 / BIP 174: CompactSize fields must use the shortest encoding,
|
|
306
327
|
// so wider prefixes for smaller values are rejected here.
|
|
307
328
|
if (num < start) throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
308
329
|
return num;
|
|
309
330
|
},
|
|
310
331
|
})
|
|
311
|
-
)
|
|
332
|
+
);
|
|
333
|
+
})();
|
|
312
334
|
|
|
313
335
|
// Same thing, but in number instead of bigint. Checks for safe integer inside
|
|
314
336
|
/**
|