@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/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Audited & minimal library for creating, signing & decoding Bitcoin transactions.
|
|
|
4
4
|
|
|
5
5
|
- ๐ [**Audited**](#security) by an independent security firm
|
|
6
6
|
- โ๏ธ Create transactions, inputs, outputs, sign them
|
|
7
|
-
- ๐ก
|
|
7
|
+
- ๐ก Optional network helper; core signer works offline
|
|
8
8
|
- ๐ UTXO selection with different strategies
|
|
9
9
|
- ๐ป Classic & SegWit: P2PK, P2PKH, P2WPKH, P2SH, P2WSH, P2MS
|
|
10
10
|
- ๐งช Schnorr & Taproot BIP340/BIP341: P2TR, P2TR-NS, P2TR-MS
|
|
@@ -49,6 +49,40 @@ For React Native, you may need a [polyfill for crypto.getRandomValues](https://g
|
|
|
49
49
|
import * as btc from '@scure/btc-signer';
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Quickstart
|
|
53
|
+
|
|
54
|
+
Create a Taproot address, build a transaction spending one of its UTXOs, sign and finalize it:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import * as btc from '@scure/btc-signer';
|
|
58
|
+
import { hex } from '@scure/base';
|
|
59
|
+
|
|
60
|
+
const privKey = hex.decode('a1547d0a01c9acb2b8a4128f97bbcd74d9a5750a9a3d1571ee3d9840b41d1fbb');
|
|
61
|
+
const pubKey = btc.utils.pubSchnorr(privKey);
|
|
62
|
+
|
|
63
|
+
// Taproot payment for a single key. Also check out p2wpkh, p2sh, p2wsh & others below
|
|
64
|
+
const spend = btc.p2tr(pubKey);
|
|
65
|
+
console.log(spend.address);
|
|
66
|
+
// bc1pkw4e67whvet7q6xa854tstt7kjn9f7n3gqvyjqznezqwux0gnzhsrtlvy3
|
|
67
|
+
|
|
68
|
+
const tx = new btc.Transaction();
|
|
69
|
+
tx.addInput({
|
|
70
|
+
...spend, // adds tapInternalKey & other fields required for signing
|
|
71
|
+
txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
|
|
72
|
+
index: 0,
|
|
73
|
+
witnessUtxo: { script: spend.script, amount: 100_000n }, // amounts are always bigint sats
|
|
74
|
+
});
|
|
75
|
+
tx.addOutputAddress(spend.address!, 90_000n); // leftover 10k sats become the fee
|
|
76
|
+
tx.sign(privKey);
|
|
77
|
+
tx.finalize();
|
|
78
|
+
|
|
79
|
+
console.log(tx.id);
|
|
80
|
+
// 927bc3441c7b376a83913b534bf10430139d9f64d83cbc38eb3a4e24cce54dc5
|
|
81
|
+
console.log(tx.hex); // ready to broadcast, e.g. via net.ts EsploraProvider
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The rest of the docs cover every payment type, PSBT workflows, UTXO selection and more:
|
|
85
|
+
|
|
52
86
|
- [Payments](#payments)
|
|
53
87
|
- [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
|
|
54
88
|
- [P2PKH Public Key Hash](#p2pkh-public-key-hash)
|
|
@@ -69,6 +103,7 @@ import * as btc from '@scure/btc-signer';
|
|
|
69
103
|
- [Basic transaction sign](#basic-transaction-sign)
|
|
70
104
|
- [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
|
|
71
105
|
- [UTXO selection](#utxo-selection)
|
|
106
|
+
- [Network](#network)
|
|
72
107
|
- [MuSig2](#musig2)
|
|
73
108
|
- [P2P, ElligatorSwift, BIP324](#p2p-elligatorswift-bip324)
|
|
74
109
|
- [Ordinals and custom scripts](#ordinals-and-custom-scripts)
|
|
@@ -519,27 +554,27 @@ type DerivationPath = { fingerprint: number; path: number[] };
|
|
|
519
554
|
type TapScriptSigKey = { pubKey: Bytes; leafHash: Bytes };
|
|
520
555
|
type TapLeafScriptKey = { version: number; internalKey: Bytes; merklePath: Bytes[] };
|
|
521
556
|
type TransactionInput = {
|
|
522
|
-
txid?: Bytes
|
|
523
|
-
index?: number
|
|
524
|
-
nonWitnessUtxo?: RawTransactionBytesOrHex
|
|
525
|
-
witnessUtxo?: { script?: Bytes; amount: bigint }
|
|
557
|
+
txid?: Bytes;
|
|
558
|
+
index?: number;
|
|
559
|
+
nonWitnessUtxo?: RawTransactionBytesOrHex;
|
|
560
|
+
witnessUtxo?: { script?: Bytes; amount: bigint };
|
|
526
561
|
partialSig?: [Bytes, Bytes][]; // [PubKey, Signature]
|
|
527
|
-
sighashType?: number
|
|
528
|
-
redeemScript?: Bytes
|
|
529
|
-
witnessScript?: Bytes
|
|
562
|
+
sighashType?: number;
|
|
563
|
+
redeemScript?: Bytes;
|
|
564
|
+
witnessScript?: Bytes;
|
|
530
565
|
bip32Derivation?: [Bytes, DerivationPath | undefined][]; // [PubKey, DeriviationPath]
|
|
531
|
-
finalScriptSig?: Bytes
|
|
532
|
-
finalScriptWitness?: Bytes[]
|
|
533
|
-
porCommitment?: Bytes
|
|
534
|
-
sequence?: number
|
|
535
|
-
requiredTimeLocktime?: number
|
|
536
|
-
requiredHeightLocktime?: number
|
|
537
|
-
tapKeySig?: Bytes
|
|
566
|
+
finalScriptSig?: Bytes;
|
|
567
|
+
finalScriptWitness?: Bytes[];
|
|
568
|
+
porCommitment?: Bytes;
|
|
569
|
+
sequence?: number;
|
|
570
|
+
requiredTimeLocktime?: number;
|
|
571
|
+
requiredHeightLocktime?: number;
|
|
572
|
+
tapKeySig?: Bytes;
|
|
538
573
|
tapScriptSig?: [TapScriptSigKey, Bytes][]; // [PubKeySchnorr, LeafHash]
|
|
539
574
|
// [ControlBlock, ScriptWithVersion]
|
|
540
575
|
tapLeafScript?: [TapLeafScriptKey, Bytes][];
|
|
541
|
-
tapInternalKey?: Bytes
|
|
542
|
-
tapMerkleRoot?: Bytes
|
|
576
|
+
tapInternalKey?: Bytes;
|
|
577
|
+
tapMerkleRoot?: Bytes;
|
|
543
578
|
};
|
|
544
579
|
|
|
545
580
|
// tx.addInput(input: TransactionInput): number;
|
|
@@ -632,12 +667,12 @@ const tx = new btc.Transaction();
|
|
|
632
667
|
type Bytes = Uint8Array | string;
|
|
633
668
|
type DerivationPath = { fingerprint: number; path: number[] };
|
|
634
669
|
type TransactionOutput = {
|
|
635
|
-
script?: Bytes
|
|
636
|
-
amount?: bigint
|
|
637
|
-
redeemScript?: Bytes
|
|
638
|
-
witnessScript?: Bytes
|
|
670
|
+
script?: Bytes;
|
|
671
|
+
amount?: bigint;
|
|
672
|
+
redeemScript?: Bytes;
|
|
673
|
+
witnessScript?: Bytes;
|
|
639
674
|
bip32Derivation?: [Bytes, DerivationPath | undefined][]; // [PubKey, DeriviationPath]
|
|
640
|
-
tapInternalKey?: Bytes
|
|
675
|
+
tapInternalKey?: Bytes;
|
|
641
676
|
};
|
|
642
677
|
|
|
643
678
|
// tx.addOutput(o: TransactionOutput): number;
|
|
@@ -1025,6 +1060,97 @@ deepStrictEqual(tx.id, 'b702078d65edd65a84b2a97a669df5631b06f42a67b0d7090e540b02
|
|
|
1025
1060
|
deepStrictEqual(tx.fee, 394n);
|
|
1026
1061
|
```
|
|
1027
1062
|
|
|
1063
|
+
## Network
|
|
1064
|
+
|
|
1065
|
+
Bitcoin nodes can't be used as source-of-truth to construct transactions & get UTXOs.
|
|
1066
|
+
An extra indexer is required. Our `net.js` submodule allows to easily fetch UTXOs,
|
|
1067
|
+
balances, and other data for an address.
|
|
1068
|
+
See [README-fullnode.md](./README-fullnode.md) for details and
|
|
1069
|
+
guide on running a full node with an indexer.
|
|
1070
|
+
|
|
1071
|
+
```ts
|
|
1072
|
+
import * as btc from '@scure/btc-signer';
|
|
1073
|
+
import { EsploraProvider } from '@scure/btc-signer/net.js';
|
|
1074
|
+
import { pubECDSA } from '@scure/btc-signer/utils.js';
|
|
1075
|
+
const net = new EsploraProvider(fetch, 'http://127.0.0.1:3000');
|
|
1076
|
+
// Methods: `height`, `blockInfo`, `fee`, `balance`, `txCount`, `sendTx`, `waitForTx`, `txInfo`,
|
|
1077
|
+
// `unspent`, `transfers`, `history`, `historyMulti`.
|
|
1078
|
+
// Transient backend failures (429/5xx, dropped connections) are retried with backoff on GETs.
|
|
1079
|
+
// Long scans accept `signal` (AbortSignal) and `onProgress`; raw-tx fan-out is
|
|
1080
|
+
// bounded by `concurrency` (default 8).
|
|
1081
|
+
|
|
1082
|
+
// Get latest block.
|
|
1083
|
+
async function latestBlock() {
|
|
1084
|
+
const height = await net.height();
|
|
1085
|
+
const block = await net.blockInfo(height);
|
|
1086
|
+
return { number: block.number, hash: block.hash, timestamp: new Date(block.timestamp) };
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Get per-address transaction history.
|
|
1090
|
+
async function addressTransactions(address: string) {
|
|
1091
|
+
const txs = await net.transfers(address, { limit: 10 });
|
|
1092
|
+
return txs.map((tx) => ({ txid: tx.txid, block: tx.block, fee: tx.info.fee }));
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Stream history instead of buffering it: rows arrive newest-first while pages
|
|
1096
|
+
// are fetched, and stopping early also stops fetching.
|
|
1097
|
+
async function streamHistory(address: string) {
|
|
1098
|
+
for await (const tx of net.history(address, { onProgress: (p) => console.log(p.percent) })) {
|
|
1099
|
+
if (tx.block !== undefined && tx.block < 800_000) break;
|
|
1100
|
+
console.log(tx.txid, tx.transfers);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Merged history for a set of addresses (HD wallets): one txid-deduplicated
|
|
1105
|
+
// stream; each row lists the watched addresses participating in it.
|
|
1106
|
+
async function walletHistory(addresses: string[]) {
|
|
1107
|
+
for await (const tx of net.historyMulti(addresses)) console.log(tx.txid, tx.addresses);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Broadcast, then wait for confirmation.
|
|
1111
|
+
async function sendAndWait(rawTx: string) {
|
|
1112
|
+
const txid = await net.sendTx(rawTx);
|
|
1113
|
+
return await net.waitForTx(txid, { confirmations: 2, timeoutMs: 3_600_000 });
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Get UTXOs, select inputs, and sign. Call with a key that controls funded UTXOs.
|
|
1117
|
+
async function signSpend(privKey: Uint8Array, to: string, amount: bigint) {
|
|
1118
|
+
const spend = btc.p2wpkh(pubECDSA(privKey));
|
|
1119
|
+
const unspent = await net.unspent(spend.address!);
|
|
1120
|
+
const feePerByte = await net.fee(2);
|
|
1121
|
+
const selected = btc.selectUTXO(unspent.utxo, [{ address: to, amount }], 'default', {
|
|
1122
|
+
feePerByte,
|
|
1123
|
+
changeAddress: spend.address!,
|
|
1124
|
+
});
|
|
1125
|
+
if (!selected) throw new Error(`not enough funds for ${spend.address}`);
|
|
1126
|
+
const { tx } = selected;
|
|
1127
|
+
tx.sign(privKey);
|
|
1128
|
+
tx.finalize();
|
|
1129
|
+
return { txid: tx.id, raw: tx.hex };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// For wrapped or script spends, add caller-owned metadata before selection:
|
|
1133
|
+
// const wrapped = btc.p2sh(btc.p2wpkh(pubECDSA(privKey)));
|
|
1134
|
+
// const base = await net.unspent(wrapped.address!);
|
|
1135
|
+
// const utxo = base.utxo.map((u) => ({ ...u, redeemScript: wrapped.redeemScript }));
|
|
1136
|
+
// const selected = btc.selectUTXO(utxo, outputs, 'default', opts);
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
First argument is `fetch` API-compatible transport.
|
|
1140
|
+
We suggest using `micro-ftch` package - a wrapper, which supports kill-switch,
|
|
1141
|
+
logging, timeouts, concurrency limits, replay fixtures, and other useful network controls:
|
|
1142
|
+
|
|
1143
|
+
```ts
|
|
1144
|
+
import { ftch } from 'micro-ftch';
|
|
1145
|
+
let NETWORK_ENABLED = true;
|
|
1146
|
+
const fetcher = ftch(fetch, {
|
|
1147
|
+
isValidRequest: () => NETWORK_ENABLED,
|
|
1148
|
+
timeout: 10_000,
|
|
1149
|
+
concurrencyLimit: 4,
|
|
1150
|
+
});
|
|
1151
|
+
const net = new EsploraProvider(fetcher, 'http://127.0.0.1:3000')
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1028
1154
|
## MuSig2
|
|
1029
1155
|
|
|
1030
1156
|
MuSig2 implementation conforming to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki)
|
|
@@ -1348,12 +1474,6 @@ For this package, there are 4 dependencies; and a few dev dependencies:
|
|
|
1348
1474
|
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
|
|
1349
1475
|
- prettier, fast-check and typescript are used for code quality / test generation / ts compilation. It's hard to audit their source code thoroughly and fully because of their size
|
|
1350
1476
|
|
|
1351
|
-
## Contributing & testing
|
|
1352
|
-
|
|
1353
|
-
- `npm install && npm run build && npm test` will build the code and run tests.
|
|
1354
|
-
- `npm run lint` / `npm run format` will run linter / fix linter issues.
|
|
1355
|
-
- `npm run build:release` will build single file
|
|
1356
|
-
|
|
1357
1477
|
## Learning & documentation
|
|
1358
1478
|
|
|
1359
1479
|
There are several nice resources on the topic:
|
package/index.d.ts
CHANGED
|
@@ -29,4 +29,3 @@ export type { CustomScript, OptScript } from './payment.ts';
|
|
|
29
29
|
export { _DebugPSBT, TaprootControlBlock } from './psbt.ts';
|
|
30
30
|
export { bip32Path, Decimal, DEFAULT_SEQUENCE, PSBTCombine, SigHash } from './transaction.ts';
|
|
31
31
|
export { _cmpBig, _Estimator } from './utxo.ts';
|
|
32
|
-
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
package/musig2.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WeierstrassPoint } from '@noble/curves/abstract/weierstrass.js';
|
|
1
2
|
import { type TArg, type TRet } from './utils.ts';
|
|
2
3
|
/** Represents a pair of public and secret nonces used in MuSig2 signing. */
|
|
3
4
|
export type Nonces = {
|
|
@@ -16,6 +17,15 @@ export type DetNonce = {
|
|
|
16
17
|
/** Partial signature produced after combining all participant data. */
|
|
17
18
|
partialSig: Uint8Array;
|
|
18
19
|
};
|
|
20
|
+
/** MuSig2 key aggregation context used by signing sessions. */
|
|
21
|
+
export type KeyAggregate = {
|
|
22
|
+
/** Aggregate public key before x-only export. */
|
|
23
|
+
aggPublicKey: WeierstrassPoint<bigint>;
|
|
24
|
+
/** Accumulated sign from x-only tweaks. */
|
|
25
|
+
gAcc: bigint;
|
|
26
|
+
/** Accumulated tweak scalar. */
|
|
27
|
+
tweakAcc: bigint;
|
|
28
|
+
};
|
|
19
29
|
/**
|
|
20
30
|
* Represents an error indicating an invalid contribution from a signer.
|
|
21
31
|
* This allows pointing out which participant is malicious and what specifically is wrong.
|
|
@@ -86,11 +96,7 @@ export declare function sortKeys(publicKeys: TArg<Uint8Array[]>): TRet<Uint8Arra
|
|
|
86
96
|
* ]);
|
|
87
97
|
* ```
|
|
88
98
|
*/
|
|
89
|
-
export declare function keyAggregate(publicKeys: TArg<Uint8Array[]>, tweaks?: TArg<Uint8Array[]>, isXonly?: boolean[]):
|
|
90
|
-
aggPublicKey: import("@noble/curves/abstract/weierstrass.js").WeierstrassPoint<bigint>;
|
|
91
|
-
gAcc: bigint;
|
|
92
|
-
tweakAcc: bigint;
|
|
93
|
-
};
|
|
99
|
+
export declare function keyAggregate(publicKeys: TArg<Uint8Array[]>, tweaks?: TArg<Uint8Array[]>, isXonly?: boolean[]): KeyAggregate;
|
|
94
100
|
/**
|
|
95
101
|
* Exports the aggregate public key to a byte array.
|
|
96
102
|
* @param ctx - result of {@link keyAggregate}
|
|
@@ -283,4 +289,3 @@ export declare class Session {
|
|
|
283
289
|
* ```
|
|
284
290
|
*/
|
|
285
291
|
export declare function deterministicSign(secret: TArg<Uint8Array>, aggOtherNonce: TArg<Uint8Array>, publicKeys: TArg<Uint8Array[]>, msg: TArg<Uint8Array>, tweaks?: TArg<Uint8Array[]>, isXonly?: boolean[], rand?: TArg<Uint8Array>, fastSign?: boolean): TRet<DetNonce>;
|
|
286
|
-
//# sourceMappingURL=musig2.d.ts.map
|
package/musig2.js
CHANGED
|
@@ -2,7 +2,7 @@ import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
|
2
2
|
import { aInRange, concatBytes, equalBytes, numberToBytesBE } from '@noble/curves/utils.js';
|
|
3
3
|
import { abytes, anumber, randomBytes } from '@noble/hashes/utils.js';
|
|
4
4
|
import * as P from 'micro-packed';
|
|
5
|
-
import { compareBytes, hasEven } from "./utils.js";
|
|
5
|
+
import { compareBytes, hasEven, validateObject } from "./utils.js";
|
|
6
6
|
/**
|
|
7
7
|
* Represents an error indicating an invalid contribution from a signer.
|
|
8
8
|
* This allows pointing out which participant is malicious and what specifically is wrong.
|
|
@@ -43,6 +43,9 @@ const PUBKEY_LEN = /* @__PURE__ */ (() => secp256k1.lengths.publicKey)();
|
|
|
43
43
|
// BIP327 uses bytes(33, 0) both as cbytes_ext(Point.ZERO) for infinity and as GetSecondKey's
|
|
44
44
|
// "no second distinct key" sentinel, so this all-zero compressed slot is intentionally out-of-band.
|
|
45
45
|
const ZERO = /* @__PURE__ */ new Uint8Array(PUBKEY_LEN); // Compressed zero point
|
|
46
|
+
// Be friendly to bad ECMAScript parsers by not using bigint literals.
|
|
47
|
+
// prettier-ignore
|
|
48
|
+
const _0n = /* @__PURE__ */ BigInt(0), _1n = /* @__PURE__ */ BigInt(1);
|
|
46
49
|
// Encoding
|
|
47
50
|
// TODO: re-use in PSBT?
|
|
48
51
|
// This is BIP327's cbytes_ext/cpoint_ext adapter: normal points stay in compressed SEC1,
|
|
@@ -54,7 +57,7 @@ const compressed = /* @__PURE__ */ (() => P.apply(P.bytes(33), {
|
|
|
54
57
|
// This coder is only for stored secnonce limbs k1/k2, which BIP327 requires to be
|
|
55
58
|
// nonzero scalars in [1, n); tweak scalars use different validation because 0 is allowed there.
|
|
56
59
|
const scalar = /* @__PURE__ */ (() => P.validate(P.U256BE, (n) => {
|
|
57
|
-
aInRange('n', n,
|
|
60
|
+
aInRange('n', n, _1n, Fn.ORDER);
|
|
58
61
|
return n;
|
|
59
62
|
}))();
|
|
60
63
|
// Shared for both per-signer pubnonce bytes and aggregate aggnonce bytes. Because it accepts
|
|
@@ -172,7 +175,7 @@ function keyAggCoeffInternal(publicKey1, publicKey2, L) {
|
|
|
172
175
|
abytes(publicKey1, PUBKEY_LEN);
|
|
173
176
|
abytes(publicKey2, PUBKEY_LEN);
|
|
174
177
|
if (equalBytes(publicKey1, publicKey2))
|
|
175
|
-
return
|
|
178
|
+
return _1n;
|
|
176
179
|
return taggedInt('KeyAgg coefficient', L, publicKey1);
|
|
177
180
|
}
|
|
178
181
|
/**
|
|
@@ -223,6 +226,10 @@ export function keyAggregate(publicKeys, tweaks = [], isXonly = []) {
|
|
|
223
226
|
}
|
|
224
227
|
aggPublicKey = aggPublicKey.add(Pi.multiply(keyAggCoeffInternal(publicKeys[i], pk2, L)));
|
|
225
228
|
}
|
|
229
|
+
// BIP327 KeyAggInternal: "Fail if is_infinite(Q)". Computationally unreachable for
|
|
230
|
+
// hash-derived coefficients, but the spec mandates the explicit check before tweaking.
|
|
231
|
+
if (isZero(aggPublicKey))
|
|
232
|
+
throw new Error('keyAggregate: aggregate public key cannot be infinity');
|
|
226
233
|
let gAcc = Fn.ONE;
|
|
227
234
|
let tweakAcc = Fn.ZERO;
|
|
228
235
|
// Apply tweaks
|
|
@@ -257,6 +264,9 @@ export function keyAggregate(publicKeys, tweaks = [], isXonly = []) {
|
|
|
257
264
|
* ```
|
|
258
265
|
*/
|
|
259
266
|
export function keyAggExport(ctx) {
|
|
267
|
+
validateObject(ctx, {}, {}, 'ctx');
|
|
268
|
+
if (!(ctx.aggPublicKey instanceof Point))
|
|
269
|
+
throw new TypeError('"ctx.aggPublicKey" expected point, got type=' + typeof ctx.aggPublicKey);
|
|
260
270
|
// BIP327 GetXonlyPubkey returns xbytes(Q), so this is the 32-byte x-only aggregate key
|
|
261
271
|
// instead of the 33-byte compressed SEC1 form.
|
|
262
272
|
return pointToBytes(ctx.aggPublicKey);
|
|
@@ -425,6 +435,7 @@ export class Session {
|
|
|
425
435
|
* @throws If the input is invalid, such as wrong array sizes or lengths. {@link Error}
|
|
426
436
|
*/
|
|
427
437
|
constructor(aggNonce, publicKeys, msg, tweaks = [], isXonly = []) {
|
|
438
|
+
abytes(aggNonce, 66);
|
|
428
439
|
abytesArray(publicKeys, 33);
|
|
429
440
|
abytesArray(tweaks, 32);
|
|
430
441
|
aXonly(isXonly);
|
|
@@ -443,7 +454,10 @@ export class Session {
|
|
|
443
454
|
this.gAcc = gAcc;
|
|
444
455
|
this.tweakAcc = tweakAcc;
|
|
445
456
|
this.b = taggedInt('MuSig/noncecoef', aggNonce, pointToBytes(aggPublicKey), msg);
|
|
446
|
-
|
|
457
|
+
// b and the nonce points are public session values, so the faster variable-time
|
|
458
|
+
// multiplication is safe here; it also matches the reference point_mul, which
|
|
459
|
+
// maps a (negligible-probability) zero coefficient to infinity instead of failing.
|
|
460
|
+
const R = R1.add(R2.multiplyUnsafe(this.b));
|
|
447
461
|
this.R = isZero(R) ? Point.BASE : R;
|
|
448
462
|
this.e = taggedInt('BIP0340/challenge', pointToBytes(this.R), pointToBytes(aggPublicKey), msg);
|
|
449
463
|
this.tweaks = tweaks.map((t) => Uint8Array.from(t));
|
|
@@ -476,13 +490,16 @@ export class Session {
|
|
|
476
490
|
// BIP327 PartialSigVerifyInternal: `Let s = int(psig); fail if s >= n`, so s=0 must stay
|
|
477
491
|
// in the public verification equation and return false on mismatch instead of throwing.
|
|
478
492
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
479
|
-
|
|
493
|
+
// Verification only handles public data (nonces, pubkeys, hash-derived scalars),
|
|
494
|
+
// so the faster variable-time multiplications are safe here; they also match the
|
|
495
|
+
// reference point_mul, which maps zero scalars to infinity instead of failing.
|
|
496
|
+
const Re_s_ = R1.add(R2.multiplyUnsafe(b));
|
|
480
497
|
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
481
498
|
const P = Point.fromBytes(publicKey);
|
|
482
499
|
const a = this.getSessionKeyAggCoeff(P);
|
|
483
|
-
const g = Fn.mul(evenScalar(Q,
|
|
500
|
+
const g = Fn.mul(evenScalar(Q, _1n), gAcc);
|
|
484
501
|
const left = Point.BASE.multiplyUnsafe(s);
|
|
485
|
-
const right = Re_s.add(P.
|
|
502
|
+
const right = Re_s.add(P.multiplyUnsafe(Fn.mul(e, Fn.mul(a, g))));
|
|
486
503
|
return left.equals(right);
|
|
487
504
|
}
|
|
488
505
|
/**
|
|
@@ -510,7 +527,7 @@ export class Session {
|
|
|
510
527
|
if (!Fn.isValid(k1_))
|
|
511
528
|
throw new Error('wrong k1');
|
|
512
529
|
if (!Fn.isValid(k2_))
|
|
513
|
-
throw new Error('wrong
|
|
530
|
+
throw new Error('wrong k2');
|
|
514
531
|
const k1 = evenScalar(R, k1_);
|
|
515
532
|
const k2 = evenScalar(R, k2_);
|
|
516
533
|
const d_ = Fn.fromBytes(secret);
|
|
@@ -521,7 +538,7 @@ export class Session {
|
|
|
521
538
|
if (!equalBytes(pk, originalPk))
|
|
522
539
|
throw new Error('Public key does not match nonceGen argument');
|
|
523
540
|
const a = this.getSessionKeyAggCoeff(P);
|
|
524
|
-
const g = evenScalar(Q,
|
|
541
|
+
const g = evenScalar(Q, _1n);
|
|
525
542
|
const d = Fn.mul(g, Fn.mul(gAcc, d_));
|
|
526
543
|
/// k1 + (b*k2) + (e*a*d)
|
|
527
544
|
const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
|
|
@@ -582,14 +599,14 @@ export class Session {
|
|
|
582
599
|
if (partialSigs.length < 1)
|
|
583
600
|
throw new RangeError('partialSigs.length must be >= 1');
|
|
584
601
|
const { Q, tweakAcc, R, e } = this;
|
|
585
|
-
let s =
|
|
602
|
+
let s = _0n;
|
|
586
603
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
587
604
|
const si = Fn.fromBytes(partialSigs[i], true);
|
|
588
605
|
if (!Fn.isValid(si))
|
|
589
606
|
throw new InvalidContributionErr(i, 'psig');
|
|
590
607
|
s = Fn.add(s, si);
|
|
591
608
|
}
|
|
592
|
-
const g = evenScalar(Q,
|
|
609
|
+
const g = evenScalar(Q, _1n);
|
|
593
610
|
s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
|
|
594
611
|
return concatBytes(pointToBytes(R), Fn.toBytes(s));
|
|
595
612
|
}
|
|
@@ -653,4 +670,3 @@ export function deterministicSign(secret, aggOtherNonce, publicKeys, msg, tweaks
|
|
|
653
670
|
const partialSig = session.sign(secretNonce, secret, fastSign);
|
|
654
671
|
return { publicNonce, partialSig };
|
|
655
672
|
}
|
|
656
|
-
//# sourceMappingURL=musig2.js.map
|