@scure/btc-signer 1.1.0 → 1.2.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 +151 -12
- package/index.ts +531 -119
- package/{index.js → lib/esm/index.js} +509 -135
- package/lib/esm/package.json +1 -0
- package/{index.d.ts → lib/index.d.ts} +650 -596
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3035 -0
- package/package.json +26 -18
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,21 +5,22 @@ Audited & minimal library for creating, signing & decoding Bitcoin transactions.
|
|
|
5
5
|
- 🔒 [**Audited**](#security) by an independent security firm
|
|
6
6
|
- ✍️ Create transactions, inputs, outputs, sign them
|
|
7
7
|
- 📡 No network code: simplified audits and offline usage
|
|
8
|
+
- 🔀 UTXO selection with different strategies
|
|
8
9
|
- 🎻 Classic & SegWit: P2PK, P2PKH, P2WPKH, P2SH, P2WSH, P2MS
|
|
9
10
|
- 🧪 Schnorr & Taproot BIP340/BIP341: P2TR, P2TR-NS, P2TR-MS
|
|
10
11
|
- 📨 BIP174 PSBT
|
|
11
|
-
- 👥 Multisig support
|
|
12
12
|
- 🪶 ~2600 lines
|
|
13
13
|
|
|
14
14
|
Initial development has been funded by [Ryan Shea](https://shea.io). Check out [the demo](https://signerdemo.micro-btc.dev/) & [its github](https://github.com/shea256/micro-btc-web-demo).
|
|
15
15
|
|
|
16
16
|
### This library belongs to _scure_
|
|
17
17
|
|
|
18
|
-
> **scure** —
|
|
18
|
+
> **scure** — audited micro-libraries.
|
|
19
19
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
20
|
+
- Zero or minimal dependencies
|
|
21
|
+
- Highly readable TypeScript / JS code
|
|
22
|
+
- PGP-signed releases and transparent NPM builds
|
|
23
|
+
- Check out [homepage](https://paulmillr.com/noble/#scure) & all libraries:
|
|
23
24
|
[base](https://github.com/paulmillr/scure-base),
|
|
24
25
|
[bip32](https://github.com/paulmillr/scure-bip32),
|
|
25
26
|
[bip39](https://github.com/paulmillr/scure-bip39),
|
|
@@ -39,8 +40,6 @@ import * as btc from '@scure/btc-signer';
|
|
|
39
40
|
// import * as btc from "npm:@scure/btc-signer@1.0.0"; // Deno
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
### Table of Contents
|
|
43
|
-
|
|
44
43
|
- [Payments](#payments)
|
|
45
44
|
- [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
|
|
46
45
|
- [P2PKH Public Key Hash](#p2pkh-public-key-hash)
|
|
@@ -52,17 +51,22 @@ import * as btc from '@scure/btc-signer';
|
|
|
52
51
|
- [P2TR Taproot](#p2tr-taproot)
|
|
53
52
|
- [P2TR-NS Taproot multisig](#p2tr-ns-taproot-multisig)
|
|
54
53
|
- [P2TR-MS Taproot M-of-N multisig](#p2tr-ms-taproot-m-of-n-multisig)
|
|
54
|
+
- [P2TR-PK Taproot single P2PK script](#p2tr-pk-taproot-single-p2pk-script)
|
|
55
55
|
- [Transaction](#transaction)
|
|
56
56
|
- [Encode/decode](#encodedecode)
|
|
57
57
|
- [Inputs](#inputs)
|
|
58
58
|
- [Outputs](#outputs)
|
|
59
59
|
- [Basic transaction sign](#basic-transaction-sign)
|
|
60
60
|
- [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
|
|
61
|
+
- [UTXO selection](#utxo-selection)
|
|
61
62
|
- [Utils](#utils)
|
|
62
63
|
- [getAddress](#getaddress)
|
|
63
64
|
- [WIF](#wif)
|
|
64
65
|
- [Script](#script)
|
|
65
66
|
- [OutScript](#outscript)
|
|
67
|
+
- [Security](#security)
|
|
68
|
+
- [Supply chain security](#supply-chain-security)
|
|
69
|
+
- [License](#license)
|
|
66
70
|
|
|
67
71
|
## Payments
|
|
68
72
|
|
|
@@ -530,7 +534,7 @@ type TransactionOutput = {
|
|
|
530
534
|
|
|
531
535
|
tx.addOutput(o: TransactionOutput): number;
|
|
532
536
|
tx.updateOutput(idx: number, output: TransactionOutput);
|
|
533
|
-
tx.addOutputAddress(address: string, amount:
|
|
537
|
+
tx.addOutputAddress(address: string, amount: bigint, network = NETWORK): number;
|
|
534
538
|
|
|
535
539
|
const compressed = hex.decode(
|
|
536
540
|
'030000000000000000000000000000000000000000000000000000000000000001'
|
|
@@ -623,8 +627,14 @@ const hdkey = bip32.HDKey.fromExtendedKey(epriv, testnet.bip32);
|
|
|
623
627
|
// const seed = 'cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T';
|
|
624
628
|
const tx = new btc.Transaction();
|
|
625
629
|
// A creator creating a PSBT for a transaction which creates the following outputs:
|
|
626
|
-
tx.addOutput({
|
|
627
|
-
|
|
630
|
+
tx.addOutput({
|
|
631
|
+
script: '0014d85c2b71d0060b09c9886aeb815e50991dda124d',
|
|
632
|
+
amount: btc.Decimal.decode('1.49990000'),
|
|
633
|
+
});
|
|
634
|
+
tx.addOutput({
|
|
635
|
+
script: '001400aea9a2e5f0f876a588df5546e8742d1d87008f',
|
|
636
|
+
amount: btc.Decimal.decode('1.00000000'),
|
|
637
|
+
});
|
|
628
638
|
// and spends the following inputs:
|
|
629
639
|
tx.addInput({
|
|
630
640
|
txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
|
|
@@ -695,8 +705,7 @@ tx2.updateOutput(1, {
|
|
|
695
705
|
const psbt2 = tx2.toPSBT();
|
|
696
706
|
// An updater which adds SIGHASH_ALL to the above PSBT must create this PSBT:
|
|
697
707
|
const tx3 = btc.Transaction.fromPSBT(psbt2);
|
|
698
|
-
for (let i = 0; i < tx3.inputs.length; i++)
|
|
699
|
-
tx3.updateInput(i, { sighashType: btc.SigHash.ALL });
|
|
708
|
+
for (let i = 0; i < tx3.inputs.length; i++) tx3.updateInput(i, { sighashType: btc.SigHash.ALL });
|
|
700
709
|
const psbt3 = tx3.toPSBT();
|
|
701
710
|
/*
|
|
702
711
|
Given the above updated PSBT, a signer that supports SIGHASH_ALL for P2PKH and P2WPKH spends and uses RFC6979 for nonce generation and has the following keys:
|
|
@@ -733,6 +742,134 @@ deepStrictEqual(
|
|
|
733
742
|
);
|
|
734
743
|
```
|
|
735
744
|
|
|
745
|
+
### UTXO selection
|
|
746
|
+
|
|
747
|
+
UTXO selection is the process of choosing which UTXOs to use as inputs
|
|
748
|
+
when making an on-chain bitcoin payment. The library:
|
|
749
|
+
|
|
750
|
+
- can create tx, integrated with the signer
|
|
751
|
+
- ensures change address is always specified
|
|
752
|
+
- supports bip69
|
|
753
|
+
- supports segwit + taproot
|
|
754
|
+
- calculates weight with good precision
|
|
755
|
+
- implements multiple strategies
|
|
756
|
+
|
|
757
|
+
Taproot estimation is precise, but you have to pass sighash if you want to use non-default one,
|
|
758
|
+
because it changes signature size. For complex taproot trees you need to filter tapLeafScript
|
|
759
|
+
to include only leafs which you can sign we estimate size with smallest leaf (same as finalization),
|
|
760
|
+
but in specific case keys for this leaf can be unavailable (complex multisig)
|
|
761
|
+
|
|
762
|
+
`Oldest` / `Newest` expects UTXO provided in historical order (oldest first),
|
|
763
|
+
otherwise we have no way to detect age of tx.
|
|
764
|
+
|
|
765
|
+
#### Strategies
|
|
766
|
+
|
|
767
|
+
Strategy selection is complicated. Best should be: `exactBiggest/accumSmallest`.
|
|
768
|
+
|
|
769
|
+
`exactBiggest/accumBiggest` creates tx with smallest fees,
|
|
770
|
+
but it breaks big outputs to small ones, which in the end will create
|
|
771
|
+
a lot of outputs close to dust.
|
|
772
|
+
|
|
773
|
+
- `default`: good for privacy, same as `exactBiggest/accumBiggest`
|
|
774
|
+
- `all`: send all coins to change address (consolidation)
|
|
775
|
+
- `accum`: accumulates inputs until the target value (+fees) is reached, skipping detrimental inputs
|
|
776
|
+
- `exact`: accumulates inputs until the target value (+fees) is matched, does not accumulate inputs
|
|
777
|
+
that go over the target value (within a threshold)
|
|
778
|
+
- `accumNewest`
|
|
779
|
+
- `accumOldest`
|
|
780
|
+
- `accumSmallest`
|
|
781
|
+
- `accumBiggest`
|
|
782
|
+
- `exactNewest/accumNewest`
|
|
783
|
+
- `exactNewest/accumOldest`
|
|
784
|
+
- `exactNewest/accumSmallest`
|
|
785
|
+
- `exactNewest/accumBiggest`
|
|
786
|
+
- `exactOldest/accumNewest`
|
|
787
|
+
- `exactOldest/accumOldest`
|
|
788
|
+
- `exactOldest/accumSmallest`
|
|
789
|
+
- `exactOldest/accumBiggest`
|
|
790
|
+
- `exactSmallest/accumNewest`
|
|
791
|
+
- `exactSmallest/accumOldest`
|
|
792
|
+
- `exactSmallest/accumSmallest`
|
|
793
|
+
- `exactSmallest/accumBiggest`
|
|
794
|
+
- `exactBiggest/accumNewest`
|
|
795
|
+
- `exactBiggest/accumOldest`
|
|
796
|
+
- `exactBiggest/accumSmallest`
|
|
797
|
+
- `exactBiggest/accumBiggest`
|
|
798
|
+
|
|
799
|
+
#### Example
|
|
800
|
+
|
|
801
|
+
```ts
|
|
802
|
+
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
|
|
803
|
+
const pubKey = secp256k1.getPublicKey(privKey, true);
|
|
804
|
+
const spend = btc.p2wpkh(pubKey, regtest);
|
|
805
|
+
const utxo = [
|
|
806
|
+
{
|
|
807
|
+
...spend, // add witness/redeem scripts from spend
|
|
808
|
+
// Get txid, index from explorer/network
|
|
809
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
810
|
+
index: 0,
|
|
811
|
+
// utxo tx information
|
|
812
|
+
// script can be used from spend itself or from explorer
|
|
813
|
+
witnessUtxo: { script: spend.script, amount: 100_000n }, // value in satoshi
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
...spend,
|
|
817
|
+
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
|
|
818
|
+
index: 1,
|
|
819
|
+
witnessUtxo: { script: spend.script, amount: btc.Decimal.decode('1.5') }, // value in btc
|
|
820
|
+
},
|
|
821
|
+
// {
|
|
822
|
+
// ...spend,
|
|
823
|
+
// txid: hex.decode('75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858'),
|
|
824
|
+
// index: 0,
|
|
825
|
+
// // tx hex from blockchain (required for non-SegWit UTXO)
|
|
826
|
+
// nonWitnessUtxo: hex.decode(
|
|
827
|
+
// '0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000'
|
|
828
|
+
// ),
|
|
829
|
+
// },
|
|
830
|
+
];
|
|
831
|
+
const outputs = [
|
|
832
|
+
{ address: '2MvpbAgedBzJUBZWesDwdM7p3FEkBEwq3n3', amount: 50_000n }, // amount in satoshi
|
|
833
|
+
{
|
|
834
|
+
address: 'bcrt1pw53jtgez0wf69n06fchp0ctk48620zdscnrj8heh86wykp9mv20q7vd3gm',
|
|
835
|
+
amount: btc.Decimal.decode('0.5'), // amount in btc
|
|
836
|
+
},
|
|
837
|
+
];
|
|
838
|
+
// Send all utxo to specific address (consolidation):
|
|
839
|
+
// const selected = btc.selectUTXO(utxo, [], 'all', {
|
|
840
|
+
// changeAddress: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8', ...
|
|
841
|
+
const selected = btc.selectUTXO(utxo, outputs, 'default', {
|
|
842
|
+
changeAddress: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8', // required, address to send change
|
|
843
|
+
feePerByte: 2n, // require, fee per vbyte in satoshi
|
|
844
|
+
bip69: true, // lexicographical Indexing of Transaction Inputs and Outputs
|
|
845
|
+
createTx: true, // create tx with selected inputs/outputs
|
|
846
|
+
network: regtest,
|
|
847
|
+
});
|
|
848
|
+
// NOTE: 'selected' will 'undefined' if there is not enough funds
|
|
849
|
+
deepStrictEqual(selected.fee, 394n); // estimated fee
|
|
850
|
+
deepStrictEqual(selected.change, true); // change address used
|
|
851
|
+
deepStrictEqual(selected.outputs, [
|
|
852
|
+
{ address: '2MvpbAgedBzJUBZWesDwdM7p3FEkBEwq3n3', amount: 50000n },
|
|
853
|
+
{
|
|
854
|
+
address: 'bcrt1pw53jtgez0wf69n06fchp0ctk48620zdscnrj8heh86wykp9mv20q7vd3gm',
|
|
855
|
+
amount: 50_000_000n,
|
|
856
|
+
},
|
|
857
|
+
// Change address
|
|
858
|
+
// NOTE: with bip69 it is not neccesarily last item in outputs
|
|
859
|
+
{
|
|
860
|
+
address: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8',
|
|
861
|
+
amount: 99_949_606n,
|
|
862
|
+
},
|
|
863
|
+
]);
|
|
864
|
+
// No need to create tx manually!
|
|
865
|
+
const { tx } = selected;
|
|
866
|
+
tx.sign(privKey);
|
|
867
|
+
tx.finalize();
|
|
868
|
+
deepStrictEqual(tx.id, 'b702078d65edd65a84b2a97a669df5631b06f42a67b0d7090e540b02cc65aed5');
|
|
869
|
+
// real tx fee, can be bigger than estimated, since we expect signatures of maximal size
|
|
870
|
+
deepStrictEqual(tx.fee, 394n);
|
|
871
|
+
```
|
|
872
|
+
|
|
736
873
|
## Utils
|
|
737
874
|
|
|
738
875
|
### getAddress
|
|
@@ -843,6 +980,8 @@ The library has been independently audited:
|
|
|
843
980
|
- [Changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/0.3.0..main).
|
|
844
981
|
- The audit has been funded by [Ryan Shea](https://shea.io)
|
|
845
982
|
|
|
983
|
+
UTXO selection functionality has not been audited yet.
|
|
984
|
+
|
|
846
985
|
### Supply chain security
|
|
847
986
|
|
|
848
987
|
1. **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.
|