@scure/btc-signer 1.8.0 → 2.0.0-beta.1
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 +15 -12
- package/_type_test.js +10 -12
- package/_type_test.js.map +1 -1
- package/index.js +17 -61
- package/index.js.map +1 -1
- package/musig2.d.ts +2 -2
- package/musig2.d.ts.map +1 -1
- package/musig2.js +93 -91
- package/musig2.js.map +1 -1
- package/p2p.d.ts +7 -8
- package/p2p.d.ts.map +1 -1
- package/p2p.js +32 -38
- package/p2p.js.map +1 -1
- package/package.json +33 -70
- package/payment.js +73 -96
- package/payment.js.map +1 -1
- package/psbt.d.ts +224 -224
- package/psbt.d.ts.map +1 -1
- package/psbt.js +58 -63
- package/psbt.js.map +1 -1
- package/script.d.ts +125 -120
- package/script.d.ts.map +1 -1
- package/script.js +46 -146
- package/script.js.map +1 -1
- package/src/_type_test.ts +2 -2
- package/src/musig2.ts +45 -53
- package/src/p2p.ts +31 -43
- package/src/psbt.ts +7 -7
- package/src/script.ts +29 -26
- package/src/transaction.ts +31 -24
- package/src/utils.ts +45 -26
- package/src/utxo.ts +1 -1
- package/transaction.d.ts +23 -28
- package/transaction.d.ts.map +1 -1
- package/transaction.js +140 -154
- package/transaction.js.map +1 -1
- package/utils.d.ts +13 -7
- package/utils.d.ts.map +1 -1
- package/utils.js +60 -62
- package/utils.js.map +1 -1
- package/utxo.d.ts +45 -45
- package/utxo.d.ts.map +1 -1
- package/utxo.js +63 -59
- package/utxo.js.map +1 -1
- package/esm/_type_test.d.ts +0 -2
- package/esm/_type_test.d.ts.map +0 -1
- package/esm/_type_test.js +0 -57
- package/esm/_type_test.js.map +0 -1
- package/esm/index.d.ts +0 -22
- package/esm/index.d.ts.map +0 -1
- package/esm/index.js +0 -22
- package/esm/index.js.map +0 -1
- package/esm/musig2.d.ts +0 -148
- package/esm/musig2.d.ts.map +0 -1
- package/esm/musig2.js +0 -405
- package/esm/musig2.js.map +0 -1
- package/esm/p2p.d.ts +0 -38
- package/esm/p2p.d.ts.map +0 -1
- package/esm/p2p.js +0 -156
- package/esm/p2p.js.map +0 -1
- package/esm/package.json +0 -1
- package/esm/payment.d.ts +0 -200
- package/esm/payment.d.ts.map +0 -1
- package/esm/payment.js +0 -697
- package/esm/payment.js.map +0 -1
- package/esm/psbt.d.ts +0 -820
- package/esm/psbt.d.ts.map +0 -1
- package/esm/psbt.js +0 -409
- package/esm/psbt.js.map +0 -1
- package/esm/script.d.ts +0 -170
- package/esm/script.d.ts.map +0 -1
- package/esm/script.js +0 -356
- package/esm/script.js.map +0 -1
- package/esm/transaction.d.ts +0 -171
- package/esm/transaction.d.ts.map +0 -1
- package/esm/transaction.js +0 -1182
- package/esm/transaction.js.map +0 -1
- package/esm/utils.d.ts +0 -35
- package/esm/utils.d.ts.map +0 -1
- package/esm/utils.js +0 -118
- package/esm/utils.js.map +0 -1
- package/esm/utxo.d.ts +0 -198
- package/esm/utxo.d.ts.map +0 -1
- package/esm/utxo.js +0 -461
- package/esm/utxo.js.map +0 -1
- package/src/package.json +0 -3
package/src/musig2.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
bytesToNumberBE,
|
|
5
|
-
concatBytes,
|
|
6
|
-
equalBytes,
|
|
7
|
-
numberToBytesBE,
|
|
8
|
-
} from '@noble/curves/abstract/utils';
|
|
9
|
-
import { schnorr, secp256k1 } from '@noble/curves/secp256k1';
|
|
10
|
-
import { abytes, anumber } from '@noble/hashes/_assert';
|
|
11
|
-
import { randomBytes } from '@noble/hashes/utils';
|
|
1
|
+
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
|
|
3
|
+
import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
|
|
12
4
|
import * as P from 'micro-packed';
|
|
13
|
-
import { compareBytes } from './utils.ts';
|
|
5
|
+
import { compareBytes, hasEven } from './utils.ts';
|
|
14
6
|
|
|
15
7
|
/*
|
|
16
8
|
MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
|
|
@@ -46,20 +38,20 @@ export class InvalidContributionErr extends Error {
|
|
|
46
38
|
|
|
47
39
|
// Utils
|
|
48
40
|
const { taggedHash, pointToBytes } = schnorr.utils;
|
|
49
|
-
const Point = secp256k1.
|
|
41
|
+
const Point = secp256k1.Point;
|
|
50
42
|
type Point = typeof Point.BASE;
|
|
51
|
-
const
|
|
43
|
+
const Fn = Point.Fn;
|
|
44
|
+
const PUBKEY_LEN = secp256k1.lengths.publicKey!;
|
|
52
45
|
const ZERO = new Uint8Array(PUBKEY_LEN); // Compressed zero point
|
|
53
|
-
const SECP_N = secp256k1.CURVE.n;
|
|
54
46
|
|
|
55
47
|
// Encoding
|
|
56
48
|
// TODO: re-use in PSBT?
|
|
57
49
|
const compressed = P.apply(P.bytes(33), {
|
|
58
|
-
decode: (p: Point) => (isZero(p) ? ZERO : p.
|
|
59
|
-
encode: (b: Uint8Array) => (equalBytes(b, ZERO) ? Point.ZERO : Point.
|
|
50
|
+
decode: (p: Point) => (isZero(p) ? ZERO : p.toBytes(true)),
|
|
51
|
+
encode: (b: Uint8Array) => (equalBytes(b, ZERO) ? Point.ZERO : Point.fromBytes(b)),
|
|
60
52
|
});
|
|
61
53
|
const scalar = P.validate(P.U256BE, (n) => {
|
|
62
|
-
aInRange('n', n, 1n,
|
|
54
|
+
aInRange('n', n, 1n, Fn.ORDER);
|
|
63
55
|
return n;
|
|
64
56
|
});
|
|
65
57
|
const PubNonce = P.struct({ R1: compressed, R2: compressed });
|
|
@@ -82,10 +74,9 @@ function aXonly(lst: boolean[]) {
|
|
|
82
74
|
});
|
|
83
75
|
}
|
|
84
76
|
|
|
85
|
-
const modN = (x: bigint) => mod(x, SECP_N);
|
|
86
77
|
const taggedInt = (tag: string, ...messages: Uint8Array[]) =>
|
|
87
|
-
|
|
88
|
-
const evenScalar = (p: Point, n: bigint) => (p.
|
|
78
|
+
Fn.create(Fn.fromBytes(taggedHash(tag, ...messages), true));
|
|
79
|
+
const evenScalar = (p: Point, n: bigint) => (hasEven(p.y) ? n : Fn.neg(n));
|
|
89
80
|
|
|
90
81
|
// Short utility for compat with reference implementation
|
|
91
82
|
export function IndividualPubkey(seckey: Uint8Array): Uint8Array {
|
|
@@ -159,23 +150,22 @@ export function keyAggregate(
|
|
|
159
150
|
for (let i = 0; i < publicKeys.length; i++) {
|
|
160
151
|
let Pi;
|
|
161
152
|
try {
|
|
162
|
-
Pi = Point.
|
|
153
|
+
Pi = Point.fromBytes(publicKeys[i]);
|
|
163
154
|
} catch (error) {
|
|
164
155
|
throw new InvalidContributionErr(i, 'pubkey');
|
|
165
156
|
}
|
|
166
157
|
aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
|
|
167
158
|
}
|
|
168
|
-
let gAcc =
|
|
169
|
-
let tweakAcc =
|
|
159
|
+
let gAcc = Fn.ONE;
|
|
160
|
+
let tweakAcc = Fn.ZERO;
|
|
170
161
|
// Apply tweaks
|
|
171
162
|
for (let i = 0; i < tweaks.length; i++) {
|
|
172
|
-
const g = isXonly[i] && !aggPublicKey.
|
|
173
|
-
const t =
|
|
174
|
-
aInRange('tweak', t, 0n, SECP_N);
|
|
163
|
+
const g = isXonly[i] && !hasEven(aggPublicKey.y) ? Fn.neg(Fn.ONE) : Fn.ONE;
|
|
164
|
+
const t = Fn.fromBytes(tweaks[i]);
|
|
175
165
|
aggPublicKey = aggPublicKey.multiply(g).add(mulBase(t));
|
|
176
166
|
if (isZero(aggPublicKey)) throw new Error('The result of tweaking cannot be infinity');
|
|
177
|
-
gAcc =
|
|
178
|
-
tweakAcc =
|
|
167
|
+
gAcc = Fn.mul(g, gAcc);
|
|
168
|
+
tweakAcc = Fn.add(t, Fn.mul(g, tweakAcc));
|
|
179
169
|
}
|
|
180
170
|
return { aggPublicKey, gAcc, tweakAcc };
|
|
181
171
|
}
|
|
@@ -184,7 +174,7 @@ export function keyAggregate(
|
|
|
184
174
|
* @param ctx The result of the keyAggregate function.
|
|
185
175
|
* @returns The aggregate public key as a byte array.
|
|
186
176
|
*/
|
|
187
|
-
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>) {
|
|
177
|
+
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): Uint8Array {
|
|
188
178
|
return pointToBytes(ctx.aggPublicKey);
|
|
189
179
|
}
|
|
190
180
|
|
|
@@ -238,7 +228,8 @@ export function nonceGen(
|
|
|
238
228
|
): Nonces {
|
|
239
229
|
abytes(publicKey, PUBKEY_LEN);
|
|
240
230
|
abytesOptional(secretKey, 32);
|
|
241
|
-
abytes(aggPublicKey
|
|
231
|
+
abytes(aggPublicKey);
|
|
232
|
+
if (![0, 32].includes(aggPublicKey.length)) throw new Error('wrong aggPublicKey');
|
|
242
233
|
abytesOptional(msg);
|
|
243
234
|
abytes(extraIn);
|
|
244
235
|
abytes(rand, 32);
|
|
@@ -246,8 +237,8 @@ export function nonceGen(
|
|
|
246
237
|
if (secretKey !== undefined) rand = aux(secretKey, rand);
|
|
247
238
|
const msgPrefixed =
|
|
248
239
|
msg !== undefined
|
|
249
|
-
? concatBytes(
|
|
250
|
-
:
|
|
240
|
+
? concatBytes(Uint8Array.of(1), numberToBytesBE(msg.length, 8), msg)
|
|
241
|
+
: Uint8Array.of(0);
|
|
251
242
|
const k1 = nonceHash(rand, publicKey, aggPublicKey, 0, msgPrefixed, extraIn);
|
|
252
243
|
const k2 = nonceHash(rand, publicKey, aggPublicKey, 1, msgPrefixed, extraIn);
|
|
253
244
|
return {
|
|
@@ -343,7 +334,7 @@ export class Session {
|
|
|
343
334
|
*/
|
|
344
335
|
private getSessionKeyAggCoeff(P: Point): bigint {
|
|
345
336
|
const { publicKeys } = this;
|
|
346
|
-
const pk = P.
|
|
337
|
+
const pk = P.toBytes(true);
|
|
347
338
|
const found = publicKeys.some((p) => equalBytes(p, pk));
|
|
348
339
|
if (!found) throw new Error("The signer's pubkey must be included in the list of pubkeys");
|
|
349
340
|
return keyAggCoeffInternal(pk, this.secondKey, this.L);
|
|
@@ -354,16 +345,16 @@ export class Session {
|
|
|
354
345
|
publicKey: Uint8Array
|
|
355
346
|
): boolean {
|
|
356
347
|
const { Q, gAcc, b, R, e } = this;
|
|
357
|
-
const s =
|
|
358
|
-
if (s
|
|
348
|
+
const s = Fn.fromBytes(partialSig, true);
|
|
349
|
+
if (!Fn.isValid(s)) return false;
|
|
359
350
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
360
351
|
const Re_s_ = R1.add(R2.multiply(b));
|
|
361
|
-
const Re_s = R.
|
|
362
|
-
const P = Point.
|
|
352
|
+
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
353
|
+
const P = Point.fromBytes(publicKey);
|
|
363
354
|
const a = this.getSessionKeyAggCoeff(P);
|
|
364
|
-
const g =
|
|
355
|
+
const g = Fn.mul(evenScalar(Q, 1n), gAcc);
|
|
365
356
|
const left = mulBase(s);
|
|
366
|
-
const right = Re_s.add(P.multiply(
|
|
357
|
+
const right = Re_s.add(P.multiply(Fn.mul(e, Fn.mul(a, g))));
|
|
367
358
|
return left.equals(right);
|
|
368
359
|
}
|
|
369
360
|
|
|
@@ -384,20 +375,21 @@ export class Session {
|
|
|
384
375
|
// zero-out the first 64 bytes of secretNonce so it cannot be reused
|
|
385
376
|
// TODO: this was in reference implementation, but feels very broken. Modifying input arguments is pretty bad.
|
|
386
377
|
secretNonce.fill(0, 0, 64);
|
|
387
|
-
|
|
388
|
-
|
|
378
|
+
if (!Fn.isValid(k1_)) throw new Error('wrong k1');
|
|
379
|
+
if (!Fn.isValid(k2_)) throw new Error('wrong k1');
|
|
389
380
|
const k1 = evenScalar(R, k1_);
|
|
390
381
|
const k2 = evenScalar(R, k2_);
|
|
391
|
-
const d_ =
|
|
392
|
-
|
|
382
|
+
const d_ = Fn.fromBytes(secret);
|
|
383
|
+
if (Fn.is0(d_)) throw new Error('wrong d_');
|
|
393
384
|
const P = mulBase(d_);
|
|
394
|
-
const pk = P.
|
|
385
|
+
const pk = P.toBytes(true);
|
|
395
386
|
if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
|
|
396
387
|
const a = this.getSessionKeyAggCoeff(P);
|
|
397
388
|
const g = evenScalar(Q, 1n);
|
|
398
|
-
const d =
|
|
399
|
-
|
|
400
|
-
const
|
|
389
|
+
const d = Fn.mul(g, Fn.mul(gAcc, d_));
|
|
390
|
+
/// k1 + (b*k2) + (e*a*d)
|
|
391
|
+
const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
|
|
392
|
+
const partialSig = Fn.toBytes(s);
|
|
401
393
|
// Skip validation in fast-sign mode
|
|
402
394
|
if (!fastSign) {
|
|
403
395
|
const publicNonce = PubNonce.encode({
|
|
@@ -448,13 +440,13 @@ export class Session {
|
|
|
448
440
|
const { Q, tweakAcc, R, e } = this;
|
|
449
441
|
let s = 0n;
|
|
450
442
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
451
|
-
const si =
|
|
452
|
-
if (si
|
|
453
|
-
s =
|
|
443
|
+
const si = Fn.fromBytes(partialSigs[i], true);
|
|
444
|
+
if (!Fn.isValid(si)) throw new InvalidContributionErr(i, 'psig');
|
|
445
|
+
s = Fn.add(s, si);
|
|
454
446
|
}
|
|
455
447
|
const g = evenScalar(Q, 1n);
|
|
456
|
-
s =
|
|
457
|
-
return concatBytes(pointToBytes(R),
|
|
448
|
+
s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
|
|
449
|
+
return concatBytes(pointToBytes(R), Fn.toBytes(s));
|
|
458
450
|
}
|
|
459
451
|
}
|
|
460
452
|
|
package/src/p2p.ts
CHANGED
|
@@ -18,22 +18,15 @@
|
|
|
18
18
|
* @module
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { FpIsSquare } from '@noble/curves/abstract/modular';
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
ensureBytes,
|
|
27
|
-
numberToBytesBE,
|
|
28
|
-
} from '@noble/curves/abstract/utils';
|
|
29
|
-
import { type ProjPointType as PointType } from '@noble/curves/abstract/weierstrass';
|
|
30
|
-
import { schnorr, secp256k1 } from '@noble/curves/secp256k1';
|
|
31
|
-
import { randomBytes } from '@noble/hashes/utils';
|
|
32
|
-
import { tagSchnorr } from './utils.ts';
|
|
33
|
-
|
|
34
|
-
const Fp = secp256k1.CURVE.Fp;
|
|
35
|
-
const Point = secp256k1.ProjectivePoint;
|
|
21
|
+
import { FpIsSquare } from '@noble/curves/abstract/modular.js';
|
|
22
|
+
import { concatBytes, abytes } from '@noble/curves/utils.js';
|
|
23
|
+
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
24
|
+
import { randomBytes } from '@noble/hashes/utils.js';
|
|
25
|
+
import { tagSchnorr, type Bytes } from './utils.ts';
|
|
36
26
|
|
|
27
|
+
const Point = secp256k1.Point;
|
|
28
|
+
const Fp = Point.Fp;
|
|
29
|
+
const Fn = Point.Fn;
|
|
37
30
|
const _1n = BigInt(1);
|
|
38
31
|
const _2n = BigInt(2);
|
|
39
32
|
|
|
@@ -86,29 +79,23 @@ export const elligatorSwift = {
|
|
|
86
79
|
return Fp.mul(w0, Fp.add(Fp.div(Fp.mul(u, t0), _2n), v));
|
|
87
80
|
},
|
|
88
81
|
// Encode public key (point or x coordinate bigint) into 64-byte pseudorandom encoding
|
|
89
|
-
encode: (x: bigint
|
|
90
|
-
if (x instanceof secp256k1.ProjectivePoint) x = x.x;
|
|
91
|
-
if (typeof x !== 'bigint') {
|
|
92
|
-
throw new Error(
|
|
93
|
-
'elligatorSwift.encode: wrong public key. Should be Projective point or x coordinate (bigint)'
|
|
94
|
-
);
|
|
95
|
-
}
|
|
82
|
+
encode: (x: bigint): Uint8Array => {
|
|
96
83
|
// 200k test cycles per keygen: avg=4 max=48
|
|
97
84
|
// seems too much, but same as for reference implementation
|
|
98
85
|
while (true) {
|
|
99
86
|
// random scalar 1..Fp.ORDER
|
|
100
|
-
const u = Fp.create(Fp.fromBytes(secp256k1.utils.
|
|
87
|
+
const u = Fp.create(Fp.fromBytes(secp256k1.utils.randomSecretKey()));
|
|
101
88
|
const ellCase = randomBytes(1)[0] & 7; // [0..8)
|
|
102
89
|
const t = elligatorSwift._inv(x, u, ellCase);
|
|
103
90
|
if (!t) continue;
|
|
104
|
-
return concatBytes(
|
|
91
|
+
return concatBytes(Fp.toBytes(u), Fp.toBytes(t));
|
|
105
92
|
}
|
|
106
93
|
},
|
|
107
94
|
// Decode elligatorSwift point to xonly
|
|
108
|
-
decode: (data:
|
|
109
|
-
const _data =
|
|
110
|
-
let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32)));
|
|
111
|
-
let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64)));
|
|
95
|
+
decode: (data: Uint8Array): Uint8Array => {
|
|
96
|
+
const _data = abytes(data, 64, 'data');
|
|
97
|
+
let u = Fp.create(Fp.fromBytes(_data.subarray(0, 32), true));
|
|
98
|
+
let t = Fp.create(Fp.fromBytes(_data.subarray(32, 64), true));
|
|
112
99
|
if (Fp.is0(u)) u = Fp.create(_1n);
|
|
113
100
|
if (Fp.is0(t)) t = Fp.create(_1n);
|
|
114
101
|
const u3 = Fp.mul(Fp.mul(u, u), u); // u**3
|
|
@@ -121,36 +108,37 @@ export const elligatorSwift = {
|
|
|
121
108
|
const y = Fp.div(Fp.add(x, t), Fp.mul(MINUS_3_SQRT, u));
|
|
122
109
|
// try different cases
|
|
123
110
|
let res = Fp.add(u, Fp.mul(Fp.mul(y, y), _4n)); // u + 4 * Y ** 2,
|
|
124
|
-
if (isValidX(res)) return
|
|
111
|
+
if (isValidX(res)) return Fp.toBytes(res);
|
|
125
112
|
res = Fp.div(Fp.sub(Fp.div(Fp.neg(x), y), u), _2n); // (-X / Y - u) / 2
|
|
126
|
-
if (isValidX(res)) return
|
|
113
|
+
if (isValidX(res)) return Fp.toBytes(res);
|
|
127
114
|
res = Fp.div(Fp.sub(Fp.div(x, y), u), _2n); // (X / Y - u) / 2
|
|
128
|
-
if (isValidX(res)) return
|
|
115
|
+
if (isValidX(res)) return Fp.toBytes(res);
|
|
129
116
|
throw new Error('elligatorSwift: cannot decode public key');
|
|
130
117
|
},
|
|
131
118
|
// Generate pair (public key, secret key)
|
|
132
119
|
keygen: () => {
|
|
133
|
-
const privateKey = secp256k1.utils.
|
|
134
|
-
const
|
|
120
|
+
const privateKey: Bytes = secp256k1.utils.randomSecretKey();
|
|
121
|
+
const p = Point.BASE.multiply(Point.Fn.fromBytes(privateKey));
|
|
122
|
+
const publicKey: Bytes = elligatorSwift.encode(p.x);
|
|
135
123
|
return { privateKey, publicKey };
|
|
136
124
|
},
|
|
137
125
|
// Generates shared secret between a pub key and a priv key
|
|
138
|
-
getSharedSecret: (privateKeyA:
|
|
126
|
+
getSharedSecret: (privateKeyA: Uint8Array, publicKeyB: Uint8Array): Bytes => {
|
|
139
127
|
const pub = elligatorSwift.decode(publicKeyB);
|
|
140
|
-
const priv =
|
|
128
|
+
const priv = abytes(privateKeyA, 32, 'privKey');
|
|
141
129
|
const point = schnorr.utils.lift_x(Fp.fromBytes(pub));
|
|
142
|
-
const d =
|
|
143
|
-
return
|
|
130
|
+
const d = Fn.fromBytes(priv);
|
|
131
|
+
return Fp.toBytes(point.multiply(d).x);
|
|
144
132
|
},
|
|
145
133
|
// BIP324 shared secret
|
|
146
134
|
getSharedSecretBip324: (
|
|
147
|
-
privateKeyOurs:
|
|
148
|
-
publicKeyTheirs:
|
|
149
|
-
publicKeyOurs:
|
|
135
|
+
privateKeyOurs: Uint8Array,
|
|
136
|
+
publicKeyTheirs: Uint8Array,
|
|
137
|
+
publicKeyOurs: Uint8Array,
|
|
150
138
|
initiating: boolean
|
|
151
|
-
) => {
|
|
152
|
-
const ours =
|
|
153
|
-
const theirs =
|
|
139
|
+
): Uint8Array => {
|
|
140
|
+
const ours = abytes(publicKeyOurs, undefined, 'publicKeyOurs');
|
|
141
|
+
const theirs = abytes(publicKeyTheirs, undefined, 'publicKeyTheirs');
|
|
154
142
|
const ecdhPoint = elligatorSwift.getSharedSecret(privateKeyOurs, theirs);
|
|
155
143
|
const pubs = initiating ? [ours, theirs] : [theirs, ours];
|
|
156
144
|
return tagSchnorr('bip324_ellswift_xonly_ecdh', ...pubs, ecdhPoint);
|
package/src/psbt.ts
CHANGED
|
@@ -14,13 +14,13 @@ import { type Bytes, compareBytes, equalBytes, PubT, validatePubkey } from './ut
|
|
|
14
14
|
// PSBT BIP174, BIP370, BIP371
|
|
15
15
|
|
|
16
16
|
// Can be 33 or 64 bytes
|
|
17
|
-
const PubKeyECDSA: P.CoderType<
|
|
17
|
+
const PubKeyECDSA: P.CoderType<Bytes> = P.validate(P.bytes(null), (pub) =>
|
|
18
18
|
validatePubkey(pub, PubT.ecdsa)
|
|
19
19
|
);
|
|
20
|
-
const PubKeySchnorr: P.CoderType<
|
|
20
|
+
const PubKeySchnorr: P.CoderType<Bytes> = P.validate(P.bytes(32), (pub) =>
|
|
21
21
|
validatePubkey(pub, PubT.schnorr)
|
|
22
22
|
);
|
|
23
|
-
const SignatureSchnorr: P.CoderType<
|
|
23
|
+
const SignatureSchnorr: P.CoderType<Bytes> = P.validate(P.bytes(null), (sig) => {
|
|
24
24
|
if (sig.length !== 64 && sig.length !== 65)
|
|
25
25
|
throw new Error('Schnorr signature should be 64 or 65 bytes long');
|
|
26
26
|
return sig;
|
|
@@ -61,9 +61,9 @@ const tapTree = P.array(
|
|
|
61
61
|
})
|
|
62
62
|
);
|
|
63
63
|
|
|
64
|
-
const BytesInf: P.CoderType<
|
|
65
|
-
const Bytes20: P.CoderType<
|
|
66
|
-
const Bytes32: P.CoderType<
|
|
64
|
+
const BytesInf: P.CoderType<Bytes> = P.bytes(null); // Bytes will conflict with Bytes type
|
|
65
|
+
const Bytes20: P.CoderType<Bytes> = P.bytes(20);
|
|
66
|
+
const Bytes32: P.CoderType<Bytes> = P.bytes(32);
|
|
67
67
|
// versionsRequiringExclusing = !versionsAllowsInclusion (as set)
|
|
68
68
|
// {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
|
|
69
69
|
// SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
|
|
@@ -180,7 +180,7 @@ type PSBTKeyMap = Record<string, PSBTKeyMapInfo>;
|
|
|
180
180
|
const PSBTUnknownKey: P.CoderType<
|
|
181
181
|
P.StructInput<{
|
|
182
182
|
type: number;
|
|
183
|
-
key:
|
|
183
|
+
key: Bytes;
|
|
184
184
|
}>
|
|
185
185
|
> = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
|
|
186
186
|
type PSBTUnknownFields = { unknown?: [P.UnwrapCoder<typeof PSBTUnknownKey>, Bytes][] };
|
package/src/script.ts
CHANGED
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
import * as P from 'micro-packed';
|
|
2
|
-
import { isBytes } from './utils.ts';
|
|
2
|
+
import { isBytes, reverseObject, type ValueOf, type Bytes } from './utils.ts';
|
|
3
3
|
|
|
4
4
|
export const MAX_SCRIPT_BYTE_LENGTH = 520;
|
|
5
5
|
|
|
6
6
|
// prettier-ignore
|
|
7
|
-
export
|
|
8
|
-
OP_0
|
|
9
|
-
RESERVED
|
|
10
|
-
OP_1, OP_2, OP_3, OP_4, OP_5, OP_6, OP_7, OP_8,
|
|
11
|
-
|
|
7
|
+
export const OP = {
|
|
8
|
+
OP_0: 0, PUSHDATA1: 76, PUSHDATA2: 77, PUSHDATA4: 78, '1NEGATE': 79,
|
|
9
|
+
RESERVED: 80,
|
|
10
|
+
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,
|
|
11
|
+
OP_10: 90, OP_11: 91, OP_12: 92, OP_13: 93, OP_14: 94, OP_15: 95, OP_16: 96,
|
|
12
12
|
// Control
|
|
13
|
-
NOP, VER, IF, NOTIF, VERIF, VERNOTIF, ELSE, ENDIF, VERIFY, RETURN,
|
|
13
|
+
NOP: 97, VER: 98, IF: 99, NOTIF: 100, VERIF: 101, VERNOTIF: 102, ELSE: 103, ENDIF: 104, VERIFY: 105, RETURN: 106,
|
|
14
14
|
// Stack
|
|
15
|
-
TOALTSTACK, FROMALTSTACK, '2DROP', '2DUP', '3DUP', '2OVER', '2ROT', '2SWAP',
|
|
16
|
-
IFDUP, DEPTH, DROP, DUP, NIP, OVER, PICK, ROLL, ROT, SWAP, TUCK,
|
|
15
|
+
TOALTSTACK: 107, FROMALTSTACK: 108, '2DROP': 109, '2DUP': 110, '3DUP': 111, '2OVER': 112, '2ROT': 113, '2SWAP': 114,
|
|
16
|
+
IFDUP: 115, DEPTH: 116, DROP: 117, DUP: 118, NIP: 119, OVER: 120, PICK: 121, ROLL: 122, ROT: 123, SWAP: 124, TUCK: 125,
|
|
17
17
|
// Splice
|
|
18
|
-
CAT, SUBSTR, LEFT, RIGHT, SIZE,
|
|
18
|
+
CAT: 126, SUBSTR: 127, LEFT: 128, RIGHT: 129, SIZE: 130,
|
|
19
19
|
// Boolean logic
|
|
20
|
-
INVERT, AND, OR, XOR, EQUAL, EQUALVERIFY, RESERVED1, RESERVED2,
|
|
21
|
-
|
|
22
|
-
'1ADD', '1SUB', '2MUL', '2DIV',
|
|
23
|
-
NEGATE, ABS, NOT, '0NOTEQUAL',
|
|
24
|
-
ADD, SUB, MUL, DIV, MOD, LSHIFT, RSHIFT, BOOLAND, BOOLOR,
|
|
25
|
-
NUMEQUAL, NUMEQUALVERIFY, NUMNOTEQUAL, LESSTHAN, GREATERTHAN,
|
|
26
|
-
LESSTHANOREQUAL, GREATERTHANOREQUAL, MIN, MAX, WITHIN,
|
|
20
|
+
INVERT: 131, AND: 132, OR: 133, XOR: 134, EQUAL: 135, EQUALVERIFY: 136, RESERVED1: 137, RESERVED2: 138,
|
|
21
|
+
// Numbers
|
|
22
|
+
'1ADD': 139, '1SUB': 140, '2MUL': 141, '2DIV': 142,
|
|
23
|
+
NEGATE: 143, ABS: 144, NOT: 145, '0NOTEQUAL': 146,
|
|
24
|
+
ADD: 147, SUB: 148, MUL: 149, DIV: 150, MOD: 151, LSHIFT: 152, RSHIFT: 153, BOOLAND: 154, BOOLOR: 155,
|
|
25
|
+
NUMEQUAL: 156, NUMEQUALVERIFY: 157, NUMNOTEQUAL: 158, LESSTHAN: 159, GREATERTHAN: 160,
|
|
26
|
+
LESSTHANOREQUAL: 161, GREATERTHANOREQUAL: 162, MIN: 163, MAX: 164, WITHIN: 165,
|
|
27
27
|
// Crypto
|
|
28
|
-
RIPEMD160, SHA1, SHA256, HASH160, HASH256, CODESEPARATOR,
|
|
29
|
-
CHECKSIG, CHECKSIGVERIFY, CHECKMULTISIG, CHECKMULTISIGVERIFY,
|
|
28
|
+
RIPEMD160: 166, SHA1: 167, SHA256: 168, HASH160: 169, HASH256: 170, CODESEPARATOR: 171,
|
|
29
|
+
CHECKSIG: 172, CHECKSIGVERIFY: 173, CHECKMULTISIG: 174, CHECKMULTISIGVERIFY: 175,
|
|
30
30
|
// Expansion
|
|
31
|
-
NOP1, CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY, NOP4, NOP5, NOP6, NOP7, NOP8, NOP9, NOP10,
|
|
31
|
+
NOP1: 176, CHECKLOCKTIMEVERIFY: 177, CHECKSEQUENCEVERIFY: 178, NOP4: 179, NOP5: 180, NOP6: 181, NOP7: 182, NOP8: 183, NOP9: 184, NOP10: 185,
|
|
32
32
|
// BIP 342
|
|
33
|
-
CHECKSIGADD,
|
|
33
|
+
CHECKSIGADD: 186,
|
|
34
34
|
// Invalid
|
|
35
|
-
INVALID
|
|
36
|
-
}
|
|
35
|
+
INVALID: 255,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const OPNames = reverseObject(OP);
|
|
39
|
+
export type OP = ValueOf<typeof OP>;
|
|
37
40
|
|
|
38
41
|
export type ScriptOP = keyof typeof OP | Uint8Array | number;
|
|
39
42
|
export type ScriptType = ScriptOP[];
|
|
@@ -157,7 +160,7 @@ export const Script: P.CoderType<ScriptType> = P.wrap({
|
|
|
157
160
|
} else if (OP.OP_1 <= cur && cur <= OP.OP_16) {
|
|
158
161
|
out.push(cur - (OP.OP_1 - 1));
|
|
159
162
|
} else {
|
|
160
|
-
const op =
|
|
163
|
+
const op = OPNames[cur] as keyof typeof OP;
|
|
161
164
|
if (op === undefined) throw new Error(`Unknown opcode=${cur.toString(16)}`);
|
|
162
165
|
out.push(op);
|
|
163
166
|
}
|
|
@@ -200,10 +203,10 @@ export const CompactSize: P.CoderType<bigint> = P.wrap({
|
|
|
200
203
|
export const CompactSizeLen: P.CoderType<number> = P.apply(CompactSize, P.coders.numberBigint);
|
|
201
204
|
|
|
202
205
|
// ui8a of size <CompactSize>
|
|
203
|
-
export const VarBytes: P.CoderType<
|
|
206
|
+
export const VarBytes: P.CoderType<Bytes> = P.bytes(CompactSize);
|
|
204
207
|
|
|
205
208
|
// SegWit v0 stack of witness buffers
|
|
206
|
-
export const RawWitness: P.CoderType<
|
|
209
|
+
export const RawWitness: P.CoderType<Bytes[]> = P.array(CompactSizeLen, VarBytes);
|
|
207
210
|
|
|
208
211
|
// Array of size <CompactSize>
|
|
209
212
|
export const BTCArray = <T>(t: P.CoderType<T>): P.CoderType<T[]> => P.array(CompactSize, t);
|
package/src/transaction.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import * as u from './utils.ts';
|
|
15
15
|
import { type Bytes, NETWORK, concatBytes, equalBytes, isBytes } from './utils.ts';
|
|
16
16
|
|
|
17
|
-
const EMPTY32 = new Uint8Array(32);
|
|
17
|
+
const EMPTY32: Uint8Array = new Uint8Array(32);
|
|
18
18
|
const EMPTY_OUTPUT: P.UnwrapCoder<typeof RawOutput> = {
|
|
19
19
|
amount: 0xffffffffffffffffn,
|
|
20
20
|
script: P.EMPTY,
|
|
@@ -66,6 +66,8 @@ export interface TxOpts {
|
|
|
66
66
|
lockTime?: number;
|
|
67
67
|
PSBTVersion?: number;
|
|
68
68
|
// Flags
|
|
69
|
+
// Allow non-standard transaction version
|
|
70
|
+
allowUnknownVersion?: boolean;
|
|
69
71
|
// Allow output scripts to be unknown scripts (probably unspendable)
|
|
70
72
|
/** @deprecated Use `allowUnknownOutputs` */
|
|
71
73
|
allowUnknowOutput?: boolean;
|
|
@@ -92,24 +94,26 @@ export interface TxOpts {
|
|
|
92
94
|
* Internal, exported only for backwards-compat. Use `SigHash` instead.
|
|
93
95
|
* @deprecated
|
|
94
96
|
*/
|
|
95
|
-
export
|
|
96
|
-
DEFAULT,
|
|
97
|
-
ALL,
|
|
98
|
-
NONE,
|
|
99
|
-
SINGLE,
|
|
100
|
-
ANYONECANPAY
|
|
101
|
-
}
|
|
97
|
+
export const SignatureHash = {
|
|
98
|
+
DEFAULT: 0,
|
|
99
|
+
ALL: 1,
|
|
100
|
+
NONE: 2,
|
|
101
|
+
SINGLE: 3,
|
|
102
|
+
ANYONECANPAY: 0x80,
|
|
103
|
+
};
|
|
102
104
|
|
|
103
|
-
export
|
|
104
|
-
DEFAULT
|
|
105
|
-
ALL
|
|
106
|
-
NONE
|
|
107
|
-
SINGLE
|
|
108
|
-
DEFAULT_ANYONECANPAY
|
|
109
|
-
ALL_ANYONECANPAY
|
|
110
|
-
NONE_ANYONECANPAY
|
|
111
|
-
SINGLE_ANYONECANPAY
|
|
112
|
-
}
|
|
105
|
+
export const SigHash = {
|
|
106
|
+
DEFAULT: SignatureHash.DEFAULT,
|
|
107
|
+
ALL: SignatureHash.ALL,
|
|
108
|
+
NONE: SignatureHash.NONE,
|
|
109
|
+
SINGLE: SignatureHash.SINGLE,
|
|
110
|
+
DEFAULT_ANYONECANPAY: SignatureHash.DEFAULT | SignatureHash.ANYONECANPAY,
|
|
111
|
+
ALL_ANYONECANPAY: SignatureHash.ALL | SignatureHash.ANYONECANPAY,
|
|
112
|
+
NONE_ANYONECANPAY: SignatureHash.NONE | SignatureHash.ANYONECANPAY,
|
|
113
|
+
SINGLE_ANYONECANPAY: SignatureHash.SINGLE | SignatureHash.ANYONECANPAY,
|
|
114
|
+
} as const;
|
|
115
|
+
export const SigHashNames = u.reverseObject(SigHash);
|
|
116
|
+
export type SigHash = u.ValueOf<typeof SigHash>;
|
|
113
117
|
|
|
114
118
|
function getTaprootKeys(
|
|
115
119
|
privKey: Bytes,
|
|
@@ -161,7 +165,7 @@ function cleanFinalInput(i: psbt.TransactionInput) {
|
|
|
161
165
|
const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
|
|
162
166
|
|
|
163
167
|
function validateSigHash(s: SigHash) {
|
|
164
|
-
if (typeof s !== 'number' || typeof
|
|
168
|
+
if (typeof s !== 'number' || typeof SigHashNames[s] !== 'string')
|
|
165
169
|
throw new Error(`Invalid SigHash=${s}`);
|
|
166
170
|
return s;
|
|
167
171
|
}
|
|
@@ -190,9 +194,6 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
|
|
|
190
194
|
opts.allowUnknownInputs = _opts.allowUnknowInput;
|
|
191
195
|
if (typeof _opts.allowUnknowOutput !== 'undefined')
|
|
192
196
|
opts.allowUnknownOutputs = _opts.allowUnknowOutput;
|
|
193
|
-
// 0 and -1 happens in tests
|
|
194
|
-
if (![-1, 0, 1, 2, 3].includes(_opts.version))
|
|
195
|
-
throw new Error(`Unknown version: ${_opts.version}`);
|
|
196
197
|
if (typeof _opts.lockTime !== 'number') throw new Error('Transaction lock time should be number');
|
|
197
198
|
P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
|
|
198
199
|
// There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
|
|
@@ -201,6 +202,7 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
|
|
|
201
202
|
throw new Error(`Unknown PSBT version ${_opts.PSBTVersion}`);
|
|
202
203
|
// Flags
|
|
203
204
|
for (const k of [
|
|
205
|
+
'allowUnknownVersion',
|
|
204
206
|
'allowUnknownOutputs',
|
|
205
207
|
'allowUnknownInputs',
|
|
206
208
|
'disableScriptCheck',
|
|
@@ -213,6 +215,13 @@ function validateOpts(opts: TxOpts): Readonly<TxOpts> {
|
|
|
213
215
|
if (typeof v !== 'boolean')
|
|
214
216
|
throw new Error(`Transation options wrong type: ${k}=${v} (${typeof v})`);
|
|
215
217
|
}
|
|
218
|
+
// 0 and -1 happens in tests
|
|
219
|
+
if (
|
|
220
|
+
_opts.allowUnknownVersion
|
|
221
|
+
? typeof _opts.version === 'number'
|
|
222
|
+
: ![-1, 0, 1, 2, 3].includes(_opts.version)
|
|
223
|
+
)
|
|
224
|
+
throw new Error(`Unknown version: ${_opts.version}`);
|
|
216
225
|
if (_opts.customScripts !== undefined) {
|
|
217
226
|
const cs = _opts.customScripts;
|
|
218
227
|
if (!Array.isArray(cs)) {
|
|
@@ -628,11 +637,9 @@ export class Transaction {
|
|
|
628
637
|
}
|
|
629
638
|
|
|
630
639
|
get hash(): string {
|
|
631
|
-
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
632
640
|
return hex.encode(u.sha256x2(this.toBytes(true)));
|
|
633
641
|
}
|
|
634
642
|
get id(): string {
|
|
635
|
-
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
636
643
|
return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
|
|
637
644
|
}
|
|
638
645
|
// Input stuff
|