@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/p2p.d.ts
CHANGED
|
@@ -17,21 +17,30 @@
|
|
|
17
17
|
*
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
|
-
import { type Bytes } from './utils.ts';
|
|
20
|
+
import { type Bytes, type TArg, type TRet } from './utils.ts';
|
|
21
21
|
/**
|
|
22
22
|
* Experimental ElligatorSwift implementation:
|
|
23
23
|
* Schnorr-like x-only ECDH with public keys indistinguishable from uniformly random bytes.
|
|
24
24
|
* Documented in BIP324.
|
|
25
|
+
* @example
|
|
26
|
+
* Encode an x-only secp256k1 public key into the 64-byte BIP324 pseudorandom form.
|
|
27
|
+
* ```ts
|
|
28
|
+
* import { bytesToNumberBE } from '@noble/curves/utils.js';
|
|
29
|
+
* import { schnorr } from '@noble/curves/secp256k1.js';
|
|
30
|
+
* import { elligatorSwift } from '@scure/btc-signer/p2p.js';
|
|
31
|
+
* const secret = schnorr.utils.randomSecretKey();
|
|
32
|
+
* const encoded = elligatorSwift.encode(bytesToNumberBE(schnorr.getPublicKey(secret)));
|
|
33
|
+
* elligatorSwift.decode(encoded);
|
|
34
|
+
* ```
|
|
25
35
|
*/
|
|
26
|
-
export declare const elligatorSwift: {
|
|
36
|
+
export declare const elligatorSwift: Readonly<{
|
|
27
37
|
_inv: (x: bigint, u: bigint, ellCase: number) => bigint | void;
|
|
28
|
-
encode: (x: bigint) => Uint8Array
|
|
29
|
-
decode: (data: Uint8Array) => Uint8Array
|
|
38
|
+
encode: (x: bigint) => TRet<Uint8Array>;
|
|
39
|
+
decode: (data: TArg<Uint8Array>) => TRet<Uint8Array>;
|
|
30
40
|
keygen: () => {
|
|
31
41
|
privateKey: Bytes;
|
|
32
42
|
publicKey: Bytes;
|
|
33
43
|
};
|
|
34
|
-
getSharedSecret: (privateKeyA: Uint8Array
|
|
35
|
-
getSharedSecretBip324: (privateKeyOurs: Uint8Array
|
|
36
|
-
}
|
|
37
|
-
//# sourceMappingURL=p2p.d.ts.map
|
|
44
|
+
getSharedSecret: (privateKeyA: TArg<Uint8Array>, publicKeyB: TArg<Uint8Array>) => TRet<Bytes>;
|
|
45
|
+
getSharedSecretBip324: (privateKeyOurs: TArg<Uint8Array>, publicKeyTheirs: TArg<Uint8Array>, publicKeyOurs: TArg<Uint8Array>, initiating: boolean) => TRet<Uint8Array>;
|
|
46
|
+
}>;
|
package/p2p.js
CHANGED
|
@@ -22,16 +22,31 @@ import { concatBytes, abytes } from '@noble/curves/utils.js';
|
|
|
22
22
|
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
23
23
|
import { randomBytes } from '@noble/hashes/utils.js';
|
|
24
24
|
import { tagSchnorr } from "./utils.js";
|
|
25
|
+
// BIP324's EllSwift formulas use full secp256k1 points for priv*G and x-only ECDH
|
|
26
|
+
// before exporting x coordinates or x-only bytes.
|
|
25
27
|
const Point = secp256k1.Point;
|
|
28
|
+
// BIP324 defines XSwiftEC over integers modulo secp256k1's field prime p, so the
|
|
29
|
+
// EllSwift u/t/x arithmetic and 32-byte field encodings in this file all go through Point.Fp.
|
|
26
30
|
const Fp = Point.Fp;
|
|
31
|
+
// EllSwift private scalars use secp256k1's subgroup order n, not the field prime p used
|
|
32
|
+
// by Point.Fp; Point.Fn is the generic scalar field object, not a secret-key validator.
|
|
27
33
|
const Fn = Point.Fn;
|
|
28
34
|
const _1n = BigInt(1);
|
|
29
35
|
const _2n = BigInt(2);
|
|
36
|
+
// BIP324's XSwiftEC uses c = sqrt(-3) mod p and chooses the square root that is
|
|
37
|
+
// itself a square, which is what Fp.sqrt(Fp.create(-3)) returns here.
|
|
30
38
|
const MINUS_3_SQRT = Fp.sqrt(Fp.create(BigInt(-3)));
|
|
31
39
|
const _3n = BigInt(3);
|
|
32
40
|
const _4n = BigInt(4);
|
|
33
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));
|
|
45
|
+
// This is the "lift_x(x) succeeds" predicate for field-normalized x values.
|
|
46
|
+
// Raw x >= p would need the full BIP340 range check before reducing modulo p.
|
|
34
47
|
const isValidX = (x) => FpIsSquare(Fp, Fp.add(Fp.mul(Fp.mul(x, x), x), _7n));
|
|
48
|
+
// BIP324's "return None if the square root does not exist" branches are modeled with
|
|
49
|
+
// undefined here; current callers only pass field-normalized values from Fp arithmetic.
|
|
35
50
|
const trySqrt = (x) => {
|
|
36
51
|
try {
|
|
37
52
|
return Fp.sqrt(x);
|
|
@@ -42,9 +57,20 @@ const trySqrt = (x) => {
|
|
|
42
57
|
* Experimental ElligatorSwift implementation:
|
|
43
58
|
* Schnorr-like x-only ECDH with public keys indistinguishable from uniformly random bytes.
|
|
44
59
|
* Documented in BIP324.
|
|
60
|
+
* @example
|
|
61
|
+
* Encode an x-only secp256k1 public key into the 64-byte BIP324 pseudorandom form.
|
|
62
|
+
* ```ts
|
|
63
|
+
* import { bytesToNumberBE } from '@noble/curves/utils.js';
|
|
64
|
+
* import { schnorr } from '@noble/curves/secp256k1.js';
|
|
65
|
+
* import { elligatorSwift } from '@scure/btc-signer/p2p.js';
|
|
66
|
+
* const secret = schnorr.utils.randomSecretKey();
|
|
67
|
+
* const encoded = elligatorSwift.encode(bytesToNumberBE(schnorr.getPublicKey(secret)));
|
|
68
|
+
* elligatorSwift.decode(encoded);
|
|
69
|
+
* ```
|
|
45
70
|
*/
|
|
46
|
-
export const elligatorSwift = {
|
|
71
|
+
export const elligatorSwift = /* @__PURE__ */ Object.freeze({
|
|
47
72
|
// (internal stuff, exported for tests only): decode(u, _inv(x, u)) = x
|
|
73
|
+
// Returns the case-selected BIP324 XSwiftECInv representative, or undefined for None.
|
|
48
74
|
_inv: (x, u, ellCase) => {
|
|
49
75
|
if (!Number.isSafeInteger(ellCase) || ellCase < 0 || ellCase > 7)
|
|
50
76
|
throw new Error(`elligatorSwift._inv: wrong case=${ellCase}`);
|
|
@@ -70,7 +96,7 @@ export const elligatorSwift = {
|
|
|
70
96
|
return; // [2 condition]
|
|
71
97
|
if (ellCase & 1 && Fp.is0(r))
|
|
72
98
|
return;
|
|
73
|
-
v = Fp.
|
|
99
|
+
v = Fp.mul(Fp.add(Fp.neg(u), Fp.div(r, s)), INV_2); // v = (-u + r / s) / 2
|
|
74
100
|
}
|
|
75
101
|
const w = trySqrt(s);
|
|
76
102
|
if (w === undefined)
|
|
@@ -79,15 +105,29 @@ export const elligatorSwift = {
|
|
|
79
105
|
const t0 = last & 1 ? Fp.add(_1n, MINUS_3_SQRT) : Fp.sub(_1n, MINUS_3_SQRT);
|
|
80
106
|
const w0 = last === 0 || last === 5 ? Fp.neg(w) : w; // -w | w
|
|
81
107
|
// w0 * (u * t0 / 2 + v)
|
|
82
|
-
return Fp.mul(w0, Fp.add(Fp.
|
|
108
|
+
return Fp.mul(w0, Fp.add(Fp.mul(Fp.mul(u, t0), INV_2), v));
|
|
83
109
|
},
|
|
84
110
|
// Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
|
|
111
|
+
// BIP324 samples encodings for x(P), so callers must pass a curve X coordinate in 0..p-1;
|
|
112
|
+
// without an explicit guard, the field helpers below interpret out-of-range x modulo p.
|
|
85
113
|
encode: (x) => {
|
|
114
|
+
// BIP324 XSwiftEC uses field elements in `0..p-1`, and ellswift_create passes `XElligatorSwift(x(P))`,
|
|
115
|
+
// so encode() must reject out-of-range x instead of silently reducing a different bigint modulo p.
|
|
116
|
+
if (!Fp.isValid(x))
|
|
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');
|
|
86
122
|
// 200k test cycles per keygen: avg=4 max=48
|
|
87
123
|
// seems too much, but same as for reference implementation
|
|
88
124
|
while (true) {
|
|
89
|
-
//
|
|
90
|
-
|
|
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;
|
|
91
131
|
const ellCase = randomBytes(1)[0] & 7; // [0..8)
|
|
92
132
|
const t = elligatorSwift._inv(x, u, ellCase);
|
|
93
133
|
if (!t)
|
|
@@ -98,6 +138,8 @@ export const elligatorSwift = {
|
|
|
98
138
|
// Decode elligatorSwift point to xonly
|
|
99
139
|
decode: (data) => {
|
|
100
140
|
const _data = abytes(data, 64, 'data');
|
|
141
|
+
// BIP324 interprets both 32-byte halves as integers modulo p before the
|
|
142
|
+
// XSwiftEC remaps below, so arbitrary 64-byte inputs are valid here.
|
|
101
143
|
let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32), true));
|
|
102
144
|
let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64), true));
|
|
103
145
|
if (Fp.is0(u))
|
|
@@ -117,16 +159,19 @@ export const elligatorSwift = {
|
|
|
117
159
|
let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
|
|
118
160
|
if (isValidX(res))
|
|
119
161
|
return Fp.toBytes(res);
|
|
120
|
-
|
|
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
|
|
121
165
|
if (isValidX(res))
|
|
122
166
|
return Fp.toBytes(res);
|
|
123
|
-
res = Fp.
|
|
167
|
+
res = Fp.mul(Fp.sub(xDivY, u), INV_2); // (X / Y - u) / 2
|
|
124
168
|
if (isValidX(res))
|
|
125
169
|
return Fp.toBytes(res);
|
|
126
170
|
throw new Error('elligatorSwift: cannot decode public key');
|
|
127
171
|
},
|
|
128
172
|
// Generate pair (public key, secret key)
|
|
129
173
|
keygen: () => {
|
|
174
|
+
// Use a subgroup-valid secp256k1 secret key, then ElligatorSwift-encode x(priv*G).
|
|
130
175
|
const privateKey = secp256k1.utils.randomSecretKey();
|
|
131
176
|
const p = Point.BASE.multiply(Point.Fn.fromBytes(privateKey));
|
|
132
177
|
const publicKey = elligatorSwift.encode(p.x);
|
|
@@ -134,6 +179,8 @@ export const elligatorSwift = {
|
|
|
134
179
|
},
|
|
135
180
|
// Generates shared secret between a pub key and a priv key
|
|
136
181
|
getSharedSecret: (privateKeyA, publicKeyB) => {
|
|
182
|
+
// decode() accepts arbitrary 64-byte ElligatorSwift encodings, but the private scalar
|
|
183
|
+
// here still follows the usual secp256k1 subgroup-secret domain (1..n-1).
|
|
137
184
|
const pub = elligatorSwift.decode(publicKeyB);
|
|
138
185
|
const priv = abytes(privateKeyA, 32, 'privKey');
|
|
139
186
|
const point = schnorr.utils.lift_x(Fp.fromBytes(pub));
|
|
@@ -142,11 +189,16 @@ export const elligatorSwift = {
|
|
|
142
189
|
},
|
|
143
190
|
// BIP324 shared secret
|
|
144
191
|
getSharedSecretBip324: (privateKeyOurs, publicKeyTheirs, publicKeyOurs, initiating) => {
|
|
145
|
-
|
|
146
|
-
|
|
192
|
+
// BIP324 Shared secret computation hashes "the exactly 64-byte public keys'
|
|
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);
|
|
198
|
+
const ours = abytes(publicKeyOurs, 64, 'publicKeyOurs');
|
|
199
|
+
const theirs = abytes(publicKeyTheirs, 64, 'publicKeyTheirs');
|
|
147
200
|
const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
|
|
148
201
|
const pubs = initiating ? [ours, theirs] : [theirs, ours];
|
|
149
202
|
return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
|
|
150
203
|
},
|
|
151
|
-
};
|
|
152
|
-
//# sourceMappingURL=p2p.js.map
|
|
204
|
+
});
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scure/btc-signer",
|
|
3
|
-
"version": "2.0
|
|
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.
|
|
21
|
-
"@scure/bip32": "
|
|
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
|
-
"typescript": "
|
|
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
|
-
"
|
|
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",
|
|
29
31
|
"format": "prettier --write src test/*.test.*",
|
|
30
|
-
"test": "node
|
|
31
|
-
"test:
|
|
32
|
-
"test:deno": "deno --allow-env --allow-read test/index.js",
|
|
33
|
-
"test:node20": "cd test; npx tsc; node compiled/test/index.js",
|
|
34
|
-
"test:big": "node test/slow.test.ts",
|
|
32
|
+
"test": "node test/index.ts",
|
|
33
|
+
"test:slow": "node test/slow.test.ts",
|
|
35
34
|
"test:extended": "node --experimental-loader ./test/bitcoinjs-test/esm-loader.js ./test/bitcoinjs-test/index.test.js"
|
|
36
35
|
},
|
|
37
36
|
"exports": {
|
|
38
37
|
".": "./index.js",
|
|
39
38
|
"./musig2.js": "./musig2.js",
|
|
39
|
+
"./net.js": "./net.js",
|
|
40
40
|
"./p2p.js": "./p2p.js",
|
|
41
41
|
"./payment.js": "./payment.js",
|
|
42
42
|
"./psbt.js": "./psbt.js",
|