@scure/btc-signer 2.2.0 โ 2.4.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 +196 -37
- package/index.d.ts +4 -4
- package/index.js +3 -4
- package/musig2.d.ts +28 -9
- package/musig2.js +46 -18
- package/net.d.ts +355 -0
- package/net.js +880 -0
- package/p2p.d.ts +0 -1
- package/p2p.js +23 -7
- package/package.json +18 -21
- package/payment.d.ts +18 -11
- package/payment.js +203 -58
- package/psbt.d.ts +680 -10
- package/psbt.js +204 -43
- package/script.d.ts +1 -2
- package/script.js +71 -59
- package/src/_type_test.ts +83 -0
- package/src/index.ts +4 -2
- package/src/musig2.ts +71 -19
- package/src/net.ts +1111 -0
- package/src/p2p.ts +22 -6
- package/src/payment.ts +233 -70
- package/src/psbt.ts +228 -39
- package/src/script.ts +57 -35
- package/src/transaction.ts +869 -168
- package/src/utils.ts +92 -4
- package/src/utxo.ts +223 -113
- package/transaction.d.ts +40 -7
- package/transaction.js +745 -151
- package/utils.d.ts +45 -2
- package/utils.js +80 -5
- package/utxo.d.ts +192 -2
- package/utxo.js +200 -99
- 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)
|
|
@@ -480,13 +515,28 @@ deepStrictEqual(hex.encode(btc.OutScript.encode(decoded)), '51024e73');
|
|
|
480
515
|
|
|
481
516
|
### Encode/decode
|
|
482
517
|
|
|
483
|
-
We support both PSBTv0 and
|
|
518
|
+
We support both PSBTv0 and PSBTv2 (there is no PSBTv1). Explicit PSBTv0 conversion removes
|
|
519
|
+
PSBTv2-only fields after translating the unsigned transaction representation.
|
|
520
|
+
|
|
521
|
+
Assigned fields from BIPs 322, 353, 372, 373, 375, and 376 are decoded explicitly and survive the
|
|
522
|
+
default path. `TxOpts.unknown` and `TxOpts.proprietary` govern only genuinely unknown and
|
|
523
|
+
proprietary records:
|
|
524
|
+
|
|
525
|
+
- `ignore` preserves them, but the library may operate without understanding future constraints.
|
|
526
|
+
- `strip` accepts and removes them; this is the default and can break interoperability with future
|
|
527
|
+
extensions.
|
|
528
|
+
- `strict` rejects them.
|
|
484
529
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
530
|
+
Undefined bits in PSBTv2 `txModifiable` follow the `unknown` policy. `strip` and `strict` can also
|
|
531
|
+
fingerprint this library or version, while `ignore` offers better forwarding compatibility at the
|
|
532
|
+
cost of interpreting less protocol state. The deprecated `allowUnknown` boolean maps `true` to
|
|
533
|
+
`ignore` and `false` to `strip`.
|
|
488
534
|
|
|
489
|
-
|
|
535
|
+
When signing an untrusted or multi-party PSBT, pass
|
|
536
|
+
`{ strictPrevoutValidation: true }` to `Transaction.fromPSBT`. This requires every input to include
|
|
537
|
+
a full `nonWitnessUtxo` matching its outpoint before any signature is produced. It prevents forged
|
|
538
|
+
`witnessUtxo` amounts from making the displayed transaction fee appear lower than the fee that the
|
|
539
|
+
valid transaction will actually pay.
|
|
490
540
|
|
|
491
541
|
```text
|
|
492
542
|
// Decode
|
|
@@ -519,27 +569,27 @@ type DerivationPath = { fingerprint: number; path: number[] };
|
|
|
519
569
|
type TapScriptSigKey = { pubKey: Bytes; leafHash: Bytes };
|
|
520
570
|
type TapLeafScriptKey = { version: number; internalKey: Bytes; merklePath: Bytes[] };
|
|
521
571
|
type TransactionInput = {
|
|
522
|
-
txid?: Bytes
|
|
523
|
-
index?: number
|
|
524
|
-
nonWitnessUtxo?: RawTransactionBytesOrHex
|
|
525
|
-
witnessUtxo?: { script?: Bytes; amount: bigint }
|
|
572
|
+
txid?: Bytes;
|
|
573
|
+
index?: number;
|
|
574
|
+
nonWitnessUtxo?: RawTransactionBytesOrHex;
|
|
575
|
+
witnessUtxo?: { script?: Bytes; amount: bigint };
|
|
526
576
|
partialSig?: [Bytes, Bytes][]; // [PubKey, Signature]
|
|
527
|
-
sighashType?: number
|
|
528
|
-
redeemScript?: Bytes
|
|
529
|
-
witnessScript?: Bytes
|
|
577
|
+
sighashType?: number;
|
|
578
|
+
redeemScript?: Bytes;
|
|
579
|
+
witnessScript?: Bytes;
|
|
530
580
|
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
|
|
581
|
+
finalScriptSig?: Bytes;
|
|
582
|
+
finalScriptWitness?: Bytes[];
|
|
583
|
+
porCommitment?: Bytes;
|
|
584
|
+
sequence?: number;
|
|
585
|
+
requiredTimeLocktime?: number;
|
|
586
|
+
requiredHeightLocktime?: number;
|
|
587
|
+
tapKeySig?: Bytes;
|
|
538
588
|
tapScriptSig?: [TapScriptSigKey, Bytes][]; // [PubKeySchnorr, LeafHash]
|
|
539
589
|
// [ControlBlock, ScriptWithVersion]
|
|
540
590
|
tapLeafScript?: [TapLeafScriptKey, Bytes][];
|
|
541
|
-
tapInternalKey?: Bytes
|
|
542
|
-
tapMerkleRoot?: Bytes
|
|
591
|
+
tapInternalKey?: Bytes;
|
|
592
|
+
tapMerkleRoot?: Bytes;
|
|
543
593
|
};
|
|
544
594
|
|
|
545
595
|
// tx.addInput(input: TransactionInput): number;
|
|
@@ -632,12 +682,12 @@ const tx = new btc.Transaction();
|
|
|
632
682
|
type Bytes = Uint8Array | string;
|
|
633
683
|
type DerivationPath = { fingerprint: number; path: number[] };
|
|
634
684
|
type TransactionOutput = {
|
|
635
|
-
script?: Bytes
|
|
636
|
-
amount?: bigint
|
|
637
|
-
redeemScript?: Bytes
|
|
638
|
-
witnessScript?: Bytes
|
|
685
|
+
script?: Bytes;
|
|
686
|
+
amount?: bigint;
|
|
687
|
+
redeemScript?: Bytes;
|
|
688
|
+
witnessScript?: Bytes;
|
|
639
689
|
bip32Derivation?: [Bytes, DerivationPath | undefined][]; // [PubKey, DeriviationPath]
|
|
640
|
-
tapInternalKey?: Bytes
|
|
690
|
+
tapInternalKey?: Bytes;
|
|
641
691
|
};
|
|
642
692
|
|
|
643
693
|
// tx.addOutput(o: TransactionOutput): number;
|
|
@@ -902,10 +952,28 @@ when making an on-chain bitcoin payment. The library:
|
|
|
902
952
|
- calculates weight with good precision
|
|
903
953
|
- implements multiple strategies
|
|
904
954
|
|
|
905
|
-
Taproot estimation is precise, but you have to pass
|
|
906
|
-
because it changes signature size.
|
|
907
|
-
|
|
908
|
-
|
|
955
|
+
Taproot estimation is precise, but you have to pass `sighashType` when using a non-default
|
|
956
|
+
sighash because it changes signature size. A Taproot input can advertise paths that the current
|
|
957
|
+
wallet cannot spend. Pass the wallet's x-only public keys as `filterTaproot` to remove unavailable
|
|
958
|
+
internal-key and script paths before selection:
|
|
959
|
+
|
|
960
|
+
```js
|
|
961
|
+
import * as btc from '@scure/btc-signer';
|
|
962
|
+
|
|
963
|
+
const select = (utxos, outputs, changeAddress, feePerByte, myPublicKey) =>
|
|
964
|
+
btc.selectUTXO(utxos, outputs, 'default', {
|
|
965
|
+
changeAddress,
|
|
966
|
+
feePerByte,
|
|
967
|
+
filterTaproot: [myPublicKey],
|
|
968
|
+
});
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
Filtering is opt-in: when `filterTaproot` is omitted, no paths are removed. The standalone
|
|
972
|
+
`btc.filterTaproot(utxos, [myPublicKey])` utility returns the same filtered candidate records.
|
|
973
|
+
Built-in `tr_ns` and `tr_ms` paths are checked directly. Custom or unknown leaves are rejected;
|
|
974
|
+
callers must filter those paths using their own semantics before invoking this helper. Estimation
|
|
975
|
+
and finalization choose the available path with the smallest complete witness, retaining the old
|
|
976
|
+
shallowest-path order for equal weights.
|
|
909
977
|
|
|
910
978
|
`Oldest` / `Newest` expects UTXO provided in historical order (oldest first),
|
|
911
979
|
otherwise we have no way to detect age of tx.
|
|
@@ -1025,11 +1093,108 @@ deepStrictEqual(tx.id, 'b702078d65edd65a84b2a97a669df5631b06f42a67b0d7090e540b02
|
|
|
1025
1093
|
deepStrictEqual(tx.fee, 394n);
|
|
1026
1094
|
```
|
|
1027
1095
|
|
|
1096
|
+
## Network
|
|
1097
|
+
|
|
1098
|
+
Bitcoin nodes can't be used as source-of-truth to construct transactions & get UTXOs.
|
|
1099
|
+
An extra indexer is required. Our `net.js` submodule allows to easily fetch UTXOs,
|
|
1100
|
+
balances, and other data for an address.
|
|
1101
|
+
See [README-fullnode.md](./README-fullnode.md) for details and
|
|
1102
|
+
guide on running a full node with an indexer.
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
import * as btc from '@scure/btc-signer';
|
|
1106
|
+
import { EsploraProvider } from '@scure/btc-signer/net.js';
|
|
1107
|
+
import { pubECDSA } from '@scure/btc-signer/utils.js';
|
|
1108
|
+
const net = new EsploraProvider(fetch, 'http://127.0.0.1:3000');
|
|
1109
|
+
// Methods: `height`, `blockInfo`, `fee`, `balance`, `txCount`, `sendTx`, `waitForTx`, `txInfo`,
|
|
1110
|
+
// `unspent`, `transfers`, `history`, `historyMulti`.
|
|
1111
|
+
// Transient backend failures (429/5xx, dropped connections) are retried with backoff on GETs.
|
|
1112
|
+
// Long scans accept `signal` (AbortSignal) and `onProgress`; raw-tx fan-out is
|
|
1113
|
+
// bounded by `concurrency` (default 8).
|
|
1114
|
+
|
|
1115
|
+
// Get latest block.
|
|
1116
|
+
async function latestBlock() {
|
|
1117
|
+
const height = await net.height();
|
|
1118
|
+
const block = await net.blockInfo(height);
|
|
1119
|
+
return { number: block.number, hash: block.hash, timestamp: new Date(block.timestamp) };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Get per-address transaction history.
|
|
1123
|
+
async function addressTransactions(address: string) {
|
|
1124
|
+
const txs = await net.transfers(address, { limit: 10 });
|
|
1125
|
+
return txs.map((tx) => ({ txid: tx.txid, block: tx.block, fee: tx.info.fee }));
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Stream history instead of buffering it: rows arrive newest-first while pages
|
|
1129
|
+
// are fetched, and stopping early also stops fetching.
|
|
1130
|
+
async function streamHistory(address: string) {
|
|
1131
|
+
for await (const tx of net.history(address, { onProgress: (p) => console.log(p.percent) })) {
|
|
1132
|
+
if (tx.block !== undefined && tx.block < 800_000) break;
|
|
1133
|
+
console.log(tx.txid, tx.transfers);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Merged history for a set of addresses (HD wallets): one txid-deduplicated
|
|
1138
|
+
// stream; each row lists the watched addresses participating in it.
|
|
1139
|
+
async function walletHistory(addresses: string[]) {
|
|
1140
|
+
for await (const tx of net.historyMulti(addresses)) console.log(tx.txid, tx.addresses);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Broadcast, then wait for confirmation.
|
|
1144
|
+
async function sendAndWait(rawTx: string) {
|
|
1145
|
+
const txid = await net.sendTx(rawTx);
|
|
1146
|
+
return await net.waitForTx(txid, { confirmations: 2, timeoutMs: 3_600_000 });
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Get UTXOs, select inputs, and sign. Call with a key that controls funded UTXOs.
|
|
1150
|
+
async function signSpend(privKey: Uint8Array, to: string, amount: bigint) {
|
|
1151
|
+
const spend = btc.p2wpkh(pubECDSA(privKey));
|
|
1152
|
+
const unspent = await net.unspent(spend.address!);
|
|
1153
|
+
const feePerByte = await net.fee(2);
|
|
1154
|
+
const selected = btc.selectUTXO(unspent.utxo, [{ address: to, amount }], 'default', {
|
|
1155
|
+
feePerByte,
|
|
1156
|
+
changeAddress: spend.address!,
|
|
1157
|
+
});
|
|
1158
|
+
if (!selected) throw new Error(`not enough funds for ${spend.address}`);
|
|
1159
|
+
const { tx } = selected;
|
|
1160
|
+
tx.sign(privKey);
|
|
1161
|
+
tx.finalize();
|
|
1162
|
+
return { txid: tx.id, raw: tx.hex };
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// For wrapped or script spends, add caller-owned metadata before selection:
|
|
1166
|
+
// const wrapped = btc.p2sh(btc.p2wpkh(pubECDSA(privKey)));
|
|
1167
|
+
// const base = await net.unspent(wrapped.address!);
|
|
1168
|
+
// const utxo = base.utxo.map((u) => ({ ...u, redeemScript: wrapped.redeemScript }));
|
|
1169
|
+
// const selected = btc.selectUTXO(utxo, outputs, 'default', opts);
|
|
1170
|
+
```
|
|
1171
|
+
|
|
1172
|
+
First argument is `fetch` API-compatible transport.
|
|
1173
|
+
We suggest using `micro-ftch` package - a wrapper, which supports kill-switch,
|
|
1174
|
+
logging, timeouts, concurrency limits, replay fixtures, and other useful network controls:
|
|
1175
|
+
|
|
1176
|
+
```ts
|
|
1177
|
+
import { ftch } from 'micro-ftch';
|
|
1178
|
+
let NETWORK_ENABLED = true;
|
|
1179
|
+
const fetcher = ftch(fetch, {
|
|
1180
|
+
isValidRequest: () => NETWORK_ENABLED,
|
|
1181
|
+
timeout: 10_000,
|
|
1182
|
+
concurrencyLimit: 4,
|
|
1183
|
+
});
|
|
1184
|
+
const net = new EsploraProvider(fetcher, 'http://127.0.0.1:3000');
|
|
1185
|
+
```
|
|
1186
|
+
|
|
1028
1187
|
## MuSig2
|
|
1029
1188
|
|
|
1030
1189
|
MuSig2 implementation conforming to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki)
|
|
1031
1190
|
is available in `@scure/btc-signer/musig2.js`. Check out [bip327-musig2.test.ts](./test/bip327-musig2.test.ts) as well:
|
|
1032
1191
|
|
|
1192
|
+
**Security warning:** every MuSig2 secret nonce must be used for exactly one partial signature.
|
|
1193
|
+
`Session.sign()` zeroes only the exact `Uint8Array` passed to it; clones, serialized values,
|
|
1194
|
+
database records, and process snapshots remain live. Reusing the same nonce in distinct sessions
|
|
1195
|
+
can reveal the signer's secret key. Stateful signers must keep one authoritative nonce record and
|
|
1196
|
+
atomically consume it before releasing a partial signature.
|
|
1197
|
+
|
|
1033
1198
|
> `npm install @noble/curves`
|
|
1034
1199
|
|
|
1035
1200
|
```ts
|
|
@@ -1348,12 +1513,6 @@ For this package, there are 4 dependencies; and a few dev dependencies:
|
|
|
1348
1513
|
- jsbt is used for benchmarking / testing / build tooling and developed by the same author
|
|
1349
1514
|
- 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
1515
|
|
|
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
1516
|
## Learning & documentation
|
|
1358
1517
|
|
|
1359
1518
|
There are several nice resources on the topic:
|
package/index.d.ts
CHANGED
|
@@ -5,9 +5,10 @@ export { multisig, p2ms, p2pk, p2pkh, p2sh, p2tr, p2tr_ms, p2tr_ns, p2tr_pk, p2w
|
|
|
5
5
|
export { CompactSize, MAX_SCRIPT_BYTE_LENGTH, OP, RawTx, RawWitness, Script, ScriptNum, } from './script.ts';
|
|
6
6
|
export type { ScriptType } from './script.ts';
|
|
7
7
|
export { getInputType, Transaction } from './transaction.ts';
|
|
8
|
-
export {
|
|
8
|
+
export type { TxOpts, Unknowns } from './transaction.ts';
|
|
9
|
+
export { NETWORK, TAPROOT_UNSPENDABLE_KEY, TEST_NETWORK, taprootNumsKey } from './utils.ts';
|
|
9
10
|
export type { TArg, TRet } from './utils.ts';
|
|
10
|
-
export { selectUTXO } from './utxo.ts';
|
|
11
|
+
export { filterTaproot, selectUTXO } from './utxo.ts';
|
|
11
12
|
/**
|
|
12
13
|
* Small collection of commonly used utility exports.
|
|
13
14
|
* @example
|
|
@@ -24,9 +25,8 @@ export declare const utils: TRet<Readonly<{
|
|
|
24
25
|
randomPrivateKeyBytes: () => TRet<Uint8Array>;
|
|
25
26
|
taprootTweakPubkey: typeof taprootTweakPubkey;
|
|
26
27
|
}>>;
|
|
27
|
-
export { _sortPubkeys, Address, combinations, getAddress, OutScript, sortedMultisig, taprootListToTree, WIF, } from './payment.ts';
|
|
28
|
+
export { _sortPubkeys, Address, combinations, getAddress, MAX_COMBINATIONS, OutScript, sortedMultisig, taprootListToTree, WIF, } from './payment.ts';
|
|
28
29
|
export type { CustomScript, OptScript } from './payment.ts';
|
|
29
30
|
export { _DebugPSBT, TaprootControlBlock } from './psbt.ts';
|
|
30
31
|
export { bip32Path, Decimal, DEFAULT_SEQUENCE, PSBTCombine, SigHash } from './transaction.ts';
|
|
31
32
|
export { _cmpBig, _Estimator } from './utxo.ts';
|
|
32
|
-
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -4,8 +4,8 @@ import { compareBytes, concatBytes, isBytes, pubSchnorr, randomPrivateKeyBytes,
|
|
|
4
4
|
export { multisig, p2ms, p2pk, p2pkh, p2sh, p2tr, p2tr_ms, p2tr_ns, p2tr_pk, p2wpkh, p2wsh } from "./payment.js";
|
|
5
5
|
export { CompactSize, MAX_SCRIPT_BYTE_LENGTH, OP, RawTx, RawWitness, Script, ScriptNum, } from "./script.js";
|
|
6
6
|
export { getInputType, Transaction } from "./transaction.js";
|
|
7
|
-
export { NETWORK, TAPROOT_UNSPENDABLE_KEY, TEST_NETWORK } from "./utils.js";
|
|
8
|
-
export { selectUTXO } from "./utxo.js";
|
|
7
|
+
export { NETWORK, TAPROOT_UNSPENDABLE_KEY, TEST_NETWORK, taprootNumsKey } from "./utils.js";
|
|
8
|
+
export { filterTaproot, selectUTXO } from "./utxo.js";
|
|
9
9
|
/**
|
|
10
10
|
* Small collection of commonly used utility exports.
|
|
11
11
|
* @example
|
|
@@ -23,10 +23,9 @@ export const utils = /* @__PURE__ */ (() => Object.freeze({
|
|
|
23
23
|
randomPrivateKeyBytes,
|
|
24
24
|
taprootTweakPubkey,
|
|
25
25
|
}))();
|
|
26
|
-
export { _sortPubkeys, Address, combinations, getAddress, OutScript, sortedMultisig, taprootListToTree, WIF, } from "./payment.js";
|
|
26
|
+
export { _sortPubkeys, Address, combinations, getAddress, MAX_COMBINATIONS, OutScript, sortedMultisig, taprootListToTree, WIF, } from "./payment.js";
|
|
27
27
|
// remove
|
|
28
28
|
export { _DebugPSBT, TaprootControlBlock } from "./psbt.js";
|
|
29
29
|
// remove
|
|
30
30
|
export { bip32Path, Decimal, DEFAULT_SEQUENCE, PSBTCombine, SigHash } from "./transaction.js";
|
|
31
31
|
export { _cmpBig, _Estimator } from "./utxo.js";
|
|
32
|
-
//# sourceMappingURL=index.js.map
|
package/musig2.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
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 = {
|
|
4
5
|
/** Public nonce that gets shared with the other participants. */
|
|
5
6
|
public: Uint8Array;
|
|
6
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Secret nonce that stays local until partial signing finishes. It MUST be consumed exactly once;
|
|
9
|
+
* never retain a copy that could be loaded for another signing session.
|
|
10
|
+
*/
|
|
7
11
|
secret: Uint8Array;
|
|
8
12
|
};
|
|
9
13
|
/**
|
|
@@ -16,6 +20,15 @@ export type DetNonce = {
|
|
|
16
20
|
/** Partial signature produced after combining all participant data. */
|
|
17
21
|
partialSig: Uint8Array;
|
|
18
22
|
};
|
|
23
|
+
/** MuSig2 key aggregation context used by signing sessions. */
|
|
24
|
+
export type KeyAggregate = {
|
|
25
|
+
/** Aggregate public key before x-only export. */
|
|
26
|
+
aggPublicKey: WeierstrassPoint<bigint>;
|
|
27
|
+
/** Accumulated sign from x-only tweaks. */
|
|
28
|
+
gAcc: bigint;
|
|
29
|
+
/** Accumulated tweak scalar. */
|
|
30
|
+
tweakAcc: bigint;
|
|
31
|
+
};
|
|
19
32
|
/**
|
|
20
33
|
* Represents an error indicating an invalid contribution from a signer.
|
|
21
34
|
* This allows pointing out which participant is malicious and what specifically is wrong.
|
|
@@ -86,11 +99,7 @@ export declare function sortKeys(publicKeys: TArg<Uint8Array[]>): TRet<Uint8Arra
|
|
|
86
99
|
* ]);
|
|
87
100
|
* ```
|
|
88
101
|
*/
|
|
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
|
-
};
|
|
102
|
+
export declare function keyAggregate(publicKeys: TArg<Uint8Array[]>, tweaks?: TArg<Uint8Array[]>, isXonly?: boolean[]): KeyAggregate;
|
|
94
103
|
/**
|
|
95
104
|
* Exports the aggregate public key to a byte array.
|
|
96
105
|
* @param ctx - result of {@link keyAggregate}
|
|
@@ -110,6 +119,12 @@ export declare function keyAggregate(publicKeys: TArg<Uint8Array[]>, tweaks?: TA
|
|
|
110
119
|
export declare function keyAggExport(ctx: ReturnType<typeof keyAggregate>): TRet<Uint8Array>;
|
|
111
120
|
/**
|
|
112
121
|
* Generates a nonce pair (public and secret) for MuSig2 signing.
|
|
122
|
+
*
|
|
123
|
+
* SECURITY: The returned secret nonce MUST be used for exactly one partial signature. Keep one
|
|
124
|
+
* authoritative copy and atomically consume it when calling {@link Session.sign}. That method
|
|
125
|
+
* zeroes only the exact `Uint8Array` passed to it; clones, serialized values, database records, and
|
|
126
|
+
* snapshots remain live. Reusing a secret nonce in distinct sessions can reveal the secret key.
|
|
127
|
+
*
|
|
113
128
|
* @param publicKey - individual public key of the signer
|
|
114
129
|
* @param secretKey - optional secret key, mixed in to blind the randomness source
|
|
115
130
|
* @param aggPublicKey - aggregate public key of all signers
|
|
@@ -221,7 +236,12 @@ export declare class Session {
|
|
|
221
236
|
/**
|
|
222
237
|
* Generates a partial signature for a given message, secret nonce,
|
|
223
238
|
* secret key, and session context.
|
|
224
|
-
*
|
|
239
|
+
*
|
|
240
|
+
* SECURITY: `secretNonce` MUST be used exactly once. This method zeroes the first 64 bytes of the
|
|
241
|
+
* supplied array, including when later validation fails, but cannot erase copies or persisted
|
|
242
|
+
* representations. Reusing those nonce scalars in a distinct session can reveal the secret key.
|
|
243
|
+
*
|
|
244
|
+
* @param secretNonce - sole authoritative secret-nonce buffer for this signing session
|
|
225
245
|
* @param secret - secret key of the signer
|
|
226
246
|
* @param fastSign - if `true`, skip the self-verification pass
|
|
227
247
|
* @returns The partial signature (Uint8Array).
|
|
@@ -241,7 +261,7 @@ export declare class Session {
|
|
|
241
261
|
partialSigVerify(partialSig: Uint8Array, pubNonces: Uint8Array[], i: number): boolean;
|
|
242
262
|
/**
|
|
243
263
|
* Aggregates partial signatures from multiple signers into a single final signature.
|
|
244
|
-
* @param partialSigs - partial
|
|
264
|
+
* @param partialSigs - exactly one positional partial signature per session participant
|
|
245
265
|
* @returns The final aggregate signature (Uint8Array).
|
|
246
266
|
* @throws If the input is invalid, such as wrong array sizes or malformed
|
|
247
267
|
* signatures. {@link Error}
|
|
@@ -283,4 +303,3 @@ export declare class Session {
|
|
|
283
303
|
* ```
|
|
284
304
|
*/
|
|
285
305
|
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);
|
|
@@ -278,6 +288,12 @@ const nonceHash = (rand, publicKey, aggPublicKey, i, msgPrefixed, extraIn) =>
|
|
|
278
288
|
taggedInt('MuSig/nonce', rand, new Uint8Array([publicKey.length]), publicKey, new Uint8Array([aggPublicKey.length]), aggPublicKey, msgPrefixed, numberToBytesBE(extraIn.length, 4), extraIn, new Uint8Array([i]));
|
|
279
289
|
/**
|
|
280
290
|
* Generates a nonce pair (public and secret) for MuSig2 signing.
|
|
291
|
+
*
|
|
292
|
+
* SECURITY: The returned secret nonce MUST be used for exactly one partial signature. Keep one
|
|
293
|
+
* authoritative copy and atomically consume it when calling {@link Session.sign}. That method
|
|
294
|
+
* zeroes only the exact `Uint8Array` passed to it; clones, serialized values, database records, and
|
|
295
|
+
* snapshots remain live. Reusing a secret nonce in distinct sessions can reveal the secret key.
|
|
296
|
+
*
|
|
281
297
|
* @param publicKey - individual public key of the signer
|
|
282
298
|
* @param secretKey - optional secret key, mixed in to blind the randomness source
|
|
283
299
|
* @param aggPublicKey - aggregate public key of all signers
|
|
@@ -425,6 +441,7 @@ export class Session {
|
|
|
425
441
|
* @throws If the input is invalid, such as wrong array sizes or lengths. {@link Error}
|
|
426
442
|
*/
|
|
427
443
|
constructor(aggNonce, publicKeys, msg, tweaks = [], isXonly = []) {
|
|
444
|
+
abytes(aggNonce, 66);
|
|
428
445
|
abytesArray(publicKeys, 33);
|
|
429
446
|
abytesArray(tweaks, 32);
|
|
430
447
|
aXonly(isXonly);
|
|
@@ -443,7 +460,10 @@ export class Session {
|
|
|
443
460
|
this.gAcc = gAcc;
|
|
444
461
|
this.tweakAcc = tweakAcc;
|
|
445
462
|
this.b = taggedInt('MuSig/noncecoef', aggNonce, pointToBytes(aggPublicKey), msg);
|
|
446
|
-
|
|
463
|
+
// b and the nonce points are public session values, so the faster variable-time
|
|
464
|
+
// multiplication is safe here; it also matches the reference point_mul, which
|
|
465
|
+
// maps a (negligible-probability) zero coefficient to infinity instead of failing.
|
|
466
|
+
const R = R1.add(R2.multiplyUnsafe(this.b));
|
|
447
467
|
this.R = isZero(R) ? Point.BASE : R;
|
|
448
468
|
this.e = taggedInt('BIP0340/challenge', pointToBytes(this.R), pointToBytes(aggPublicKey), msg);
|
|
449
469
|
this.tweaks = tweaks.map((t) => Uint8Array.from(t));
|
|
@@ -476,19 +496,27 @@ export class Session {
|
|
|
476
496
|
// BIP327 PartialSigVerifyInternal: `Let s = int(psig); fail if s >= n`, so s=0 must stay
|
|
477
497
|
// in the public verification equation and return false on mismatch instead of throwing.
|
|
478
498
|
const { R1, R2 } = PubNonce.decode(publicNonce);
|
|
479
|
-
|
|
499
|
+
// Verification only handles public data (nonces, pubkeys, hash-derived scalars),
|
|
500
|
+
// so the faster variable-time multiplications are safe here; they also match the
|
|
501
|
+
// reference point_mul, which maps zero scalars to infinity instead of failing.
|
|
502
|
+
const Re_s_ = R1.add(R2.multiplyUnsafe(b));
|
|
480
503
|
const Re_s = hasEven(R.y) ? Re_s_ : Re_s_.negate();
|
|
481
504
|
const P = Point.fromBytes(publicKey);
|
|
482
505
|
const a = this.getSessionKeyAggCoeff(P);
|
|
483
|
-
const g = Fn.mul(evenScalar(Q,
|
|
506
|
+
const g = Fn.mul(evenScalar(Q, _1n), gAcc);
|
|
484
507
|
const left = Point.BASE.multiplyUnsafe(s);
|
|
485
|
-
const right = Re_s.add(P.
|
|
508
|
+
const right = Re_s.add(P.multiplyUnsafe(Fn.mul(e, Fn.mul(a, g))));
|
|
486
509
|
return left.equals(right);
|
|
487
510
|
}
|
|
488
511
|
/**
|
|
489
512
|
* Generates a partial signature for a given message, secret nonce,
|
|
490
513
|
* secret key, and session context.
|
|
491
|
-
*
|
|
514
|
+
*
|
|
515
|
+
* SECURITY: `secretNonce` MUST be used exactly once. This method zeroes the first 64 bytes of the
|
|
516
|
+
* supplied array, including when later validation fails, but cannot erase copies or persisted
|
|
517
|
+
* representations. Reusing those nonce scalars in a distinct session can reveal the secret key.
|
|
518
|
+
*
|
|
519
|
+
* @param secretNonce - sole authoritative secret-nonce buffer for this signing session
|
|
492
520
|
* @param secret - secret key of the signer
|
|
493
521
|
* @param fastSign - if `true`, skip the self-verification pass
|
|
494
522
|
* @returns The partial signature (Uint8Array).
|
|
@@ -510,7 +538,7 @@ export class Session {
|
|
|
510
538
|
if (!Fn.isValid(k1_))
|
|
511
539
|
throw new Error('wrong k1');
|
|
512
540
|
if (!Fn.isValid(k2_))
|
|
513
|
-
throw new Error('wrong
|
|
541
|
+
throw new Error('wrong k2');
|
|
514
542
|
const k1 = evenScalar(R, k1_);
|
|
515
543
|
const k2 = evenScalar(R, k2_);
|
|
516
544
|
const d_ = Fn.fromBytes(secret);
|
|
@@ -521,7 +549,7 @@ export class Session {
|
|
|
521
549
|
if (!equalBytes(pk, originalPk))
|
|
522
550
|
throw new Error('Public key does not match nonceGen argument');
|
|
523
551
|
const a = this.getSessionKeyAggCoeff(P);
|
|
524
|
-
const g = evenScalar(Q,
|
|
552
|
+
const g = evenScalar(Q, _1n);
|
|
525
553
|
const d = Fn.mul(g, Fn.mul(gAcc, d_));
|
|
526
554
|
/// k1 + (b*k2) + (e*a*d)
|
|
527
555
|
const s = Fn.add(k1, Fn.add(Fn.mul(b, k2), Fn.mul(e, Fn.mul(a, d))));
|
|
@@ -570,26 +598,27 @@ export class Session {
|
|
|
570
598
|
}
|
|
571
599
|
/**
|
|
572
600
|
* Aggregates partial signatures from multiple signers into a single final signature.
|
|
573
|
-
* @param partialSigs - partial
|
|
601
|
+
* @param partialSigs - exactly one positional partial signature per session participant
|
|
574
602
|
* @returns The final aggregate signature (Uint8Array).
|
|
575
603
|
* @throws If the input is invalid, such as wrong array sizes or malformed
|
|
576
604
|
* signatures. {@link Error}
|
|
577
605
|
*/
|
|
578
606
|
partialSigAgg(partialSigs) {
|
|
579
607
|
abytesArray(partialSigs, 32);
|
|
580
|
-
// BIP327 PartialSigAgg
|
|
581
|
-
//
|
|
582
|
-
if (partialSigs.length
|
|
583
|
-
throw new RangeError(
|
|
608
|
+
// BIP327 PartialSigAgg consumes psig_1..u for the same u signers in session_ctx. Accepting
|
|
609
|
+
// fewer or more scalars would return a signature-shaped value for a different equation.
|
|
610
|
+
if (partialSigs.length !== this.publicKeys.length)
|
|
611
|
+
throw new RangeError(`partialSigs.length=${partialSigs.length} must equal ` +
|
|
612
|
+
`participant count=${this.publicKeys.length}`);
|
|
584
613
|
const { Q, tweakAcc, R, e } = this;
|
|
585
|
-
let s =
|
|
614
|
+
let s = _0n;
|
|
586
615
|
for (let i = 0; i < partialSigs.length; i++) {
|
|
587
616
|
const si = Fn.fromBytes(partialSigs[i], true);
|
|
588
617
|
if (!Fn.isValid(si))
|
|
589
618
|
throw new InvalidContributionErr(i, 'psig');
|
|
590
619
|
s = Fn.add(s, si);
|
|
591
620
|
}
|
|
592
|
-
const g = evenScalar(Q,
|
|
621
|
+
const g = evenScalar(Q, _1n);
|
|
593
622
|
s = Fn.add(s, Fn.mul(e, Fn.mul(g, tweakAcc))); // s + e * g * tweakAcc
|
|
594
623
|
return concatBytes(pointToBytes(R), Fn.toBytes(s));
|
|
595
624
|
}
|
|
@@ -653,4 +682,3 @@ export function deterministicSign(secret, aggOtherNonce, publicKeys, msg, tweaks
|
|
|
653
682
|
const partialSig = session.sign(secretNonce, secret, fastSign);
|
|
654
683
|
return { publicNonce, partialSig };
|
|
655
684
|
}
|
|
656
|
-
//# sourceMappingURL=musig2.js.map
|