@scure/btc-signer 1.8.1 → 2.0.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 +8 -5
- 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 -90
- 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 -103
- 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/musig2.ts +43 -50
- package/src/p2p.ts +28 -40
- package/src/psbt.ts +7 -7
- package/src/script.ts +29 -26
- package/src/transaction.ts +21 -21
- package/src/utils.ts +41 -22
- package/transaction.d.ts +22 -28
- package/transaction.d.ts.map +1 -1
- package/transaction.js +134 -151
- package/transaction.js.map +1 -1
- package/utils.d.ts +11 -5
- 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 +62 -58
- package/utxo.js.map +1 -1
- package/_type_test.d.ts +0 -2
- package/_type_test.d.ts.map +0 -1
- package/_type_test.js +0 -59
- package/_type_test.js.map +0 -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 -404
- 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 -172
- package/esm/transaction.d.ts.map +0 -1
- package/esm/transaction.js +0 -1185
- 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/_type_test.ts +0 -69
- package/src/package.json +0 -3
package/src/musig2.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
import { mod } from '@noble/curves/abstract/modular.js';
|
|
2
|
-
import {
|
|
3
|
-
aInRange,
|
|
4
|
-
bytesToNumberBE,
|
|
5
|
-
concatBytes,
|
|
6
|
-
equalBytes,
|
|
7
|
-
numberToBytesBE,
|
|
8
|
-
} from '@noble/curves/abstract/utils.js';
|
|
9
1
|
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
|
|
10
3
|
import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
|
|
11
4
|
import * as P from 'micro-packed';
|
|
12
|
-
import { compareBytes } from './utils.ts';
|
|
5
|
+
import { compareBytes, hasEven } from './utils.ts';
|
|
13
6
|
|
|
14
7
|
/*
|
|
15
8
|
MuSig2. This is not the full protocol: only an implementation of primitives from BIP-327.
|
|
@@ -45,20 +38,20 @@ export class InvalidContributionErr extends Error {
|
|
|
45
38
|
|
|
46
39
|
// Utils
|
|
47
40
|
const { taggedHash, pointToBytes } = schnorr.utils;
|
|
48
|
-
const Point = secp256k1.
|
|
41
|
+
const Point = secp256k1.Point;
|
|
49
42
|
type Point = typeof Point.BASE;
|
|
50
|
-
const
|
|
43
|
+
const Fn = Point.Fn;
|
|
44
|
+
const PUBKEY_LEN = secp256k1.lengths.publicKey!;
|
|
51
45
|
const ZERO = new Uint8Array(PUBKEY_LEN); // Compressed zero point
|
|
52
|
-
const SECP_N = secp256k1.CURVE.n;
|
|
53
46
|
|
|
54
47
|
// Encoding
|
|
55
48
|
// TODO: re-use in PSBT?
|
|
56
49
|
const compressed = P.apply(P.bytes(33), {
|
|
57
|
-
decode: (p: Point) => (isZero(p) ? ZERO : p.
|
|
58
|
-
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)),
|
|
59
52
|
});
|
|
60
53
|
const scalar = P.validate(P.U256BE, (n) => {
|
|
61
|
-
aInRange('n', n, 1n,
|
|
54
|
+
aInRange('n', n, 1n, Fn.ORDER);
|
|
62
55
|
return n;
|
|
63
56
|
});
|
|
64
57
|
const PubNonce = P.struct({ R1: compressed, R2: compressed });
|
|
@@ -81,10 +74,9 @@ function aXonly(lst: boolean[]) {
|
|
|
81
74
|
});
|
|
82
75
|
}
|
|
83
76
|
|
|
84
|
-
const modN = (x: bigint) => mod(x, SECP_N);
|
|
85
77
|
const taggedInt = (tag: string, ...messages: Uint8Array[]) =>
|
|
86
|
-
|
|
87
|
-
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));
|
|
88
80
|
|
|
89
81
|
// Short utility for compat with reference implementation
|
|
90
82
|
export function IndividualPubkey(seckey: Uint8Array): Uint8Array {
|
|
@@ -158,23 +150,22 @@ export function keyAggregate(
|
|
|
158
150
|
for (let i = 0; i < publicKeys.length; i++) {
|
|
159
151
|
let Pi;
|
|
160
152
|
try {
|
|
161
|
-
Pi = Point.
|
|
153
|
+
Pi = Point.fromBytes(publicKeys[i]);
|
|
162
154
|
} catch (error) {
|
|
163
155
|
throw new InvalidContributionErr(i, 'pubkey');
|
|
164
156
|
}
|
|
165
157
|
aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
|
|
166
158
|
}
|
|
167
|
-
let gAcc =
|
|
168
|
-
let tweakAcc =
|
|
159
|
+
let gAcc = Fn.ONE;
|
|
160
|
+
let tweakAcc = Fn.ZERO;
|
|
169
161
|
// Apply tweaks
|
|
170
162
|
for (let i = 0; i < tweaks.length; i++) {
|
|
171
|
-
const g = isXonly[i] && !aggPublicKey.
|
|
172
|
-
const t =
|
|
173
|
-
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]);
|
|
174
165
|
aggPublicKey = aggPublicKey.multiply(g).add(mulBase(t));
|
|
175
166
|
if (isZero(aggPublicKey)) throw new Error('The result of tweaking cannot be infinity');
|
|
176
|
-
gAcc =
|
|
177
|
-
tweakAcc =
|
|
167
|
+
gAcc = Fn.mul(g, gAcc);
|
|
168
|
+
tweakAcc = Fn.add(t, Fn.mul(g, tweakAcc));
|
|
178
169
|
}
|
|
179
170
|
return { aggPublicKey, gAcc, tweakAcc };
|
|
180
171
|
}
|
|
@@ -183,7 +174,7 @@ export function keyAggregate(
|
|
|
183
174
|
* @param ctx The result of the keyAggregate function.
|
|
184
175
|
* @returns The aggregate public key as a byte array.
|
|
185
176
|
*/
|
|
186
|
-
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>) {
|
|
177
|
+
export function keyAggExport(ctx: ReturnType<typeof keyAggregate>): Uint8Array {
|
|
187
178
|
return pointToBytes(ctx.aggPublicKey);
|
|
188
179
|
}
|
|
189
180
|
|
|
@@ -237,7 +228,8 @@ export function nonceGen(
|
|
|
237
228
|
): Nonces {
|
|
238
229
|
abytes(publicKey, PUBKEY_LEN);
|
|
239
230
|
abytesOptional(secretKey, 32);
|
|
240
|
-
abytes(aggPublicKey
|
|
231
|
+
abytes(aggPublicKey);
|
|
232
|
+
if (![0, 32].includes(aggPublicKey.length)) throw new Error('wrong aggPublicKey');
|
|
241
233
|
abytesOptional(msg);
|
|
242
234
|
abytes(extraIn);
|
|
243
235
|
abytes(rand, 32);
|
|
@@ -245,8 +237,8 @@ export function nonceGen(
|
|
|
245
237
|
if (secretKey !== undefined) rand = aux(secretKey, rand);
|
|
246
238
|
const msgPrefixed =
|
|
247
239
|
msg !== undefined
|
|
248
|
-
? concatBytes(
|
|
249
|
-
:
|
|
240
|
+
? concatBytes(Uint8Array.of(1), numberToBytesBE(msg.length, 8), msg)
|
|
241
|
+
: Uint8Array.of(0);
|
|
250
242
|
const k1 = nonceHash(rand, publicKey, aggPublicKey, 0, msgPrefixed, extraIn);
|
|
251
243
|
const k2 = nonceHash(rand, publicKey, aggPublicKey, 1, msgPrefixed, extraIn);
|
|
252
244
|
return {
|
|
@@ -342,7 +334,7 @@ export class Session {
|
|
|
342
334
|
*/
|
|
343
335
|
private getSessionKeyAggCoeff(P: Point): bigint {
|
|
344
336
|
const { publicKeys } = this;
|
|
345
|
-
const pk = P.
|
|
337
|
+
const pk = P.toBytes(true);
|
|
346
338
|
const found = publicKeys.some((p) => equalBytes(p, pk));
|
|
347
339
|
if (!found) throw new Error("The signer's pubkey must be included in the list of pubkeys");
|
|
348
340
|
return keyAggCoeffInternal(pk, this.secondKey, this.L);
|
|
@@ -353,16 +345,16 @@ export class Session {
|
|
|
353
345
|
publicKey: Uint8Array
|
|
354
346
|
): boolean {
|
|
355
347
|
const { Q, gAcc, b, R, e } = this;
|
|
356
|
-
const s =
|
|
357
|
-
if (s
|
|
348
|
+
const s = Fn.fromBytes(partialSig, true);
|
|
349
|
+
if (!Fn.isValid(s)) return false;
|
|
358
350
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
359
351
|
const Re_s_ = R1.add(R2.multiply(b));
|
|
360
|
-
const Re_s = R.
|
|
361
|
-
const P = Point.
|
|
352
|
+
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
353
|
+
const P = Point.fromBytes(publicKey);
|
|
362
354
|
const a = this.getSessionKeyAggCoeff(P);
|
|
363
|
-
const g =
|
|
355
|
+
const g = Fn.mul(evenScalar(Q, 1n), gAcc);
|
|
364
356
|
const left = mulBase(s);
|
|
365
|
-
const right = Re_s.add(P.multiply(
|
|
357
|
+
const right = Re_s.add(P.multiply(Fn.mul(e, Fn.mul(a, g))));
|
|
366
358
|
return left.equals(right);
|
|
367
359
|
}
|
|
368
360
|
|
|
@@ -383,20 +375,21 @@ export class Session {
|
|
|
383
375
|
// zero-out the first 64 bytes of secretNonce so it cannot be reused
|
|
384
376
|
// TODO: this was in reference implementation, but feels very broken. Modifying input arguments is pretty bad.
|
|
385
377
|
secretNonce.fill(0, 0, 64);
|
|
386
|
-
|
|
387
|
-
|
|
378
|
+
if (!Fn.isValid(k1_)) throw new Error('wrong k1');
|
|
379
|
+
if (!Fn.isValid(k2_)) throw new Error('wrong k1');
|
|
388
380
|
const k1 = evenScalar(R, k1_);
|
|
389
381
|
const k2 = evenScalar(R, k2_);
|
|
390
|
-
const d_ =
|
|
391
|
-
|
|
382
|
+
const d_ = Fn.fromBytes(secret);
|
|
383
|
+
if (Fn.is0(d_)) throw new Error('wrong d_');
|
|
392
384
|
const P = mulBase(d_);
|
|
393
|
-
const pk = P.
|
|
385
|
+
const pk = P.toBytes(true);
|
|
394
386
|
if (!equalBytes(pk, originalPk)) throw new Error('Public key does not match nonceGen argument');
|
|
395
387
|
const a = this.getSessionKeyAggCoeff(P);
|
|
396
388
|
const g = evenScalar(Q, 1n);
|
|
397
|
-
const d =
|
|
398
|
-
|
|
399
|
-
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);
|
|
400
393
|
// Skip validation in fast-sign mode
|
|
401
394
|
if (!fastSign) {
|
|
402
395
|
const publicNonce = PubNonce.encode({
|
|
@@ -447,13 +440,13 @@ export class Session {
|
|
|
447
440
|
const { Q, tweakAcc, R, e } = this;
|
|
448
441
|
let s = 0n;
|
|
449
442
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
450
|
-
const si =
|
|
451
|
-
if (si
|
|
452
|
-
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);
|
|
453
446
|
}
|
|
454
447
|
const g = evenScalar(Q, 1n);
|
|
455
|
-
s =
|
|
456
|
-
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));
|
|
457
450
|
}
|
|
458
451
|
}
|
|
459
452
|
|
package/src/p2p.ts
CHANGED
|
@@ -19,21 +19,14 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { FpIsSquare } from '@noble/curves/abstract/modular.js';
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
bytesToNumberBE,
|
|
25
|
-
concatBytes,
|
|
26
|
-
ensureBytes,
|
|
27
|
-
numberToBytesBE,
|
|
28
|
-
} from '@noble/curves/abstract/utils.js';
|
|
29
|
-
import { type ProjPointType as PointType } from '@noble/curves/abstract/weierstrass.js';
|
|
22
|
+
import { concatBytes, abytes } from '@noble/curves/utils.js';
|
|
30
23
|
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
31
24
|
import { randomBytes } from '@noble/hashes/utils.js';
|
|
32
|
-
import { tagSchnorr } from './utils.ts';
|
|
33
|
-
|
|
34
|
-
const Fp = secp256k1.CURVE.Fp;
|
|
35
|
-
const Point = secp256k1.ProjectivePoint;
|
|
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,
|
|
@@ -94,24 +94,26 @@ export interface TxOpts {
|
|
|
94
94
|
* Internal, exported only for backwards-compat. Use `SigHash` instead.
|
|
95
95
|
* @deprecated
|
|
96
96
|
*/
|
|
97
|
-
export
|
|
98
|
-
DEFAULT,
|
|
99
|
-
ALL,
|
|
100
|
-
NONE,
|
|
101
|
-
SINGLE,
|
|
102
|
-
ANYONECANPAY
|
|
103
|
-
}
|
|
97
|
+
export const SignatureHash = {
|
|
98
|
+
DEFAULT: 0,
|
|
99
|
+
ALL: 1,
|
|
100
|
+
NONE: 2,
|
|
101
|
+
SINGLE: 3,
|
|
102
|
+
ANYONECANPAY: 0x80,
|
|
103
|
+
};
|
|
104
104
|
|
|
105
|
-
export
|
|
106
|
-
DEFAULT
|
|
107
|
-
ALL
|
|
108
|
-
NONE
|
|
109
|
-
SINGLE
|
|
110
|
-
DEFAULT_ANYONECANPAY
|
|
111
|
-
ALL_ANYONECANPAY
|
|
112
|
-
NONE_ANYONECANPAY
|
|
113
|
-
SINGLE_ANYONECANPAY
|
|
114
|
-
}
|
|
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>;
|
|
115
117
|
|
|
116
118
|
function getTaprootKeys(
|
|
117
119
|
privKey: Bytes,
|
|
@@ -163,7 +165,7 @@ function cleanFinalInput(i: psbt.TransactionInput) {
|
|
|
163
165
|
const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
|
|
164
166
|
|
|
165
167
|
function validateSigHash(s: SigHash) {
|
|
166
|
-
if (typeof s !== 'number' || typeof
|
|
168
|
+
if (typeof s !== 'number' || typeof SigHashNames[s] !== 'string')
|
|
167
169
|
throw new Error(`Invalid SigHash=${s}`);
|
|
168
170
|
return s;
|
|
169
171
|
}
|
|
@@ -635,11 +637,9 @@ export class Transaction {
|
|
|
635
637
|
}
|
|
636
638
|
|
|
637
639
|
get hash(): string {
|
|
638
|
-
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
639
640
|
return hex.encode(u.sha256x2(this.toBytes(true)));
|
|
640
641
|
}
|
|
641
642
|
get id(): string {
|
|
642
|
-
if (!this.isFinal) throw new Error('Transaction is not finalized');
|
|
643
643
|
return hex.encode(u.sha256x2(this.toBytes(true)).reverse());
|
|
644
644
|
}
|
|
645
645
|
// Input stuff
|
package/src/utils.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { schnorr, secp256k1 as secp } from '@noble/curves/secp256k1.js';
|
|
2
|
+
import { bytesToNumberBE, numberToBytesBE } from '@noble/curves/utils.js';
|
|
2
3
|
import { ripemd160 } from '@noble/hashes/legacy.js';
|
|
3
4
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
4
5
|
import { utils as packedUtils, U32LE } from 'micro-packed';
|
|
5
6
|
|
|
6
7
|
export type Hex = string | Uint8Array;
|
|
7
8
|
export type Bytes = Uint8Array;
|
|
8
|
-
const Point = secp.
|
|
9
|
-
const
|
|
9
|
+
const Point = secp.Point;
|
|
10
|
+
const Fn = Point.Fn;
|
|
11
|
+
const CURVE_ORDER = Point.Fn.ORDER;
|
|
12
|
+
export const hasEven = (y: bigint) => y % 2n === 0n;
|
|
10
13
|
|
|
11
14
|
const isBytes: (a: unknown) => a is Uint8Array = packedUtils.isBytes;
|
|
12
15
|
const concatBytes: (...arrays: Uint8Array[]) => Uint8Array = packedUtils.concatBytes;
|
|
@@ -15,9 +18,9 @@ export { concatBytes, equalBytes, isBytes, sha256 };
|
|
|
15
18
|
|
|
16
19
|
export const hash160 = (msg: Uint8Array): Uint8Array => ripemd160(sha256(msg));
|
|
17
20
|
export const sha256x2 = (...msgs: Uint8Array[]): Uint8Array => sha256(sha256(concatBytes(...msgs)));
|
|
18
|
-
export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.
|
|
21
|
+
export const randomPrivateKeyBytes: () => Uint8Array = schnorr.utils.randomSecretKey;
|
|
19
22
|
export const pubSchnorr = schnorr.getPublicKey as (priv: string | Uint8Array) => Uint8Array;
|
|
20
|
-
export const pubECDSA: (privateKey:
|
|
23
|
+
export const pubECDSA: (privateKey: Uint8Array, isCompressed?: boolean) => Uint8Array =
|
|
21
24
|
secp.getPublicKey;
|
|
22
25
|
|
|
23
26
|
// low-r signature grinding. Used to reduce tx size by 1 byte.
|
|
@@ -26,35 +29,37 @@ export const pubECDSA: (privateKey: string | Uint8Array, isCompressed?: boolean)
|
|
|
26
29
|
// Not best way, but closest to bitcoin implementation (easier to check)
|
|
27
30
|
const hasLowR = (sig: { r: bigint; s: bigint }) => sig.r < CURVE_ORDER / 2n;
|
|
28
31
|
export function signECDSA(hash: Bytes, privateKey: Bytes, lowR = false): Bytes {
|
|
29
|
-
let sig = secp.sign(hash, privateKey);
|
|
32
|
+
let sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false }));
|
|
30
33
|
if (lowR && !hasLowR(sig)) {
|
|
31
34
|
const extraEntropy = new Uint8Array(32);
|
|
32
35
|
let counter = 0;
|
|
33
36
|
while (!hasLowR(sig)) {
|
|
34
37
|
extraEntropy.set(U32LE.encode(counter++));
|
|
35
|
-
sig = secp.sign(hash, privateKey, { extraEntropy });
|
|
38
|
+
sig = secp.Signature.fromBytes(secp.sign(hash, privateKey, { prehash: false, extraEntropy }));
|
|
36
39
|
if (counter > 4294967295) throw new Error('lowR counter overflow: report the error');
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
|
-
return sig.
|
|
42
|
+
return sig.toBytes('der');
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
export const signSchnorr: typeof schnorr.sign = schnorr.sign;
|
|
43
46
|
export const tagSchnorr: typeof schnorr.utils.taggedHash = schnorr.utils.taggedHash;
|
|
44
47
|
|
|
45
|
-
export
|
|
46
|
-
ecdsa,
|
|
47
|
-
schnorr,
|
|
48
|
-
}
|
|
48
|
+
export const PubT = {
|
|
49
|
+
ecdsa: 0,
|
|
50
|
+
schnorr: 1,
|
|
51
|
+
};
|
|
52
|
+
export type PubT = ValueOf<typeof PubT>;
|
|
53
|
+
|
|
49
54
|
export function validatePubkey(pub: Bytes, type: PubT): Bytes {
|
|
50
55
|
const len = pub.length;
|
|
51
56
|
if (type === PubT.ecdsa) {
|
|
52
57
|
if (len === 32) throw new Error('Expected non-Schnorr key');
|
|
53
|
-
Point.
|
|
58
|
+
Point.fromBytes(pub); // does assertValidity
|
|
54
59
|
return pub;
|
|
55
60
|
} else if (type === PubT.schnorr) {
|
|
56
61
|
if (len !== 32) throw new Error('Expected 32-byte Schnorr key');
|
|
57
|
-
schnorr.utils.lift_x(
|
|
62
|
+
schnorr.utils.lift_x(bytesToNumberBE(pub));
|
|
58
63
|
return pub;
|
|
59
64
|
} else {
|
|
60
65
|
throw new Error('Unknown key type');
|
|
@@ -64,30 +69,30 @@ export function validatePubkey(pub: Bytes, type: PubT): Bytes {
|
|
|
64
69
|
export function tapTweak(a: Bytes, b: Bytes): bigint {
|
|
65
70
|
const u = schnorr.utils;
|
|
66
71
|
const t = u.taggedHash('TapTweak', a, b);
|
|
67
|
-
const tn =
|
|
72
|
+
const tn = bytesToNumberBE(t);
|
|
68
73
|
if (tn >= CURVE_ORDER) throw new Error('tweak higher than curve order');
|
|
69
74
|
return tn;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
export function taprootTweakPrivKey(privKey: Bytes, merkleRoot: Bytes = Uint8Array.of()): Bytes {
|
|
73
78
|
const u = schnorr.utils;
|
|
74
|
-
const seckey0 =
|
|
75
|
-
const P = Point.
|
|
79
|
+
const seckey0 = bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
|
|
80
|
+
const P = Point.BASE.multiply(seckey0); // P = point_mul(G, seckey0)
|
|
76
81
|
// seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
|
|
77
|
-
const seckey = P.
|
|
82
|
+
const seckey = hasEven(P.y) ? seckey0 : Fn.neg(seckey0);
|
|
78
83
|
const xP = u.pointToBytes(P);
|
|
79
84
|
// t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)); >= SECP256K1_ORDER check
|
|
80
85
|
const t = tapTweak(xP, merkleRoot);
|
|
81
86
|
// bytes_from_int((seckey + t) % SECP256K1_ORDER)
|
|
82
|
-
return
|
|
87
|
+
return numberToBytesBE(Fn.add(seckey, t), 32);
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
|
|
86
91
|
const u = schnorr.utils;
|
|
87
92
|
const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
|
|
88
|
-
const P = u.lift_x(
|
|
89
|
-
const Q = P.add(Point.
|
|
90
|
-
const parity = Q.
|
|
93
|
+
const P = u.lift_x(bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
|
|
94
|
+
const Q = P.add(Point.BASE.multiply(t)); // Q = point_add(P, point_mul(G, t))
|
|
95
|
+
const parity = hasEven(Q.y) ? 0 : 1; // 0 if has_even_y(Q) else 1
|
|
91
96
|
return [u.pointToBytes(Q), parity]; // bytes_from_int(x(Q))
|
|
92
97
|
}
|
|
93
98
|
|
|
@@ -97,7 +102,7 @@ export function taprootTweakPubkey(pubKey: Bytes, h: Bytes): [Bytes, number] {
|
|
|
97
102
|
// It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
|
|
98
103
|
// but it's too complex to prove.
|
|
99
104
|
// Also used by bitcoin-core and bitcoinjs-lib
|
|
100
|
-
export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.
|
|
105
|
+
export const TAPROOT_UNSPENDABLE_KEY: Bytes = sha256(Point.BASE.toBytes(false));
|
|
101
106
|
|
|
102
107
|
export type BTC_NETWORK = {
|
|
103
108
|
bech32: string;
|
|
@@ -127,3 +132,17 @@ export function compareBytes(a: Bytes, b: Bytes): number {
|
|
|
127
132
|
for (let i = 0; i < len; i++) if (a[i] != b[i]) return Math.sign(a[i] - b[i]);
|
|
128
133
|
return Math.sign(a.length - b.length);
|
|
129
134
|
}
|
|
135
|
+
|
|
136
|
+
// Reverses key<->values
|
|
137
|
+
export function reverseObject<T extends Record<string, string | number>>(
|
|
138
|
+
obj: T
|
|
139
|
+
): { [K in T[keyof T]]: Extract<keyof T, string> } {
|
|
140
|
+
const res = {} as any;
|
|
141
|
+
for (const k in obj) {
|
|
142
|
+
if (res[obj[k]] !== undefined) throw new Error('duplicate key');
|
|
143
|
+
res[obj[k]] = k;
|
|
144
|
+
}
|
|
145
|
+
return res;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ValueOf<T> = T[keyof T];
|