@scure/btc-signer 1.1.1 → 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 CHANGED
@@ -5,10 +5,10 @@ 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).
@@ -40,8 +40,6 @@ import * as btc from '@scure/btc-signer';
40
40
  // import * as btc from "npm:@scure/btc-signer@1.0.0"; // Deno
41
41
  ```
42
42
 
43
- ### Table of Contents
44
-
45
43
  - [Payments](#payments)
46
44
  - [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
47
45
  - [P2PKH Public Key Hash](#p2pkh-public-key-hash)
@@ -53,17 +51,22 @@ import * as btc from '@scure/btc-signer';
53
51
  - [P2TR Taproot](#p2tr-taproot)
54
52
  - [P2TR-NS Taproot multisig](#p2tr-ns-taproot-multisig)
55
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)
56
55
  - [Transaction](#transaction)
57
56
  - [Encode/decode](#encodedecode)
58
57
  - [Inputs](#inputs)
59
58
  - [Outputs](#outputs)
60
59
  - [Basic transaction sign](#basic-transaction-sign)
61
60
  - [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
61
+ - [UTXO selection](#utxo-selection)
62
62
  - [Utils](#utils)
63
63
  - [getAddress](#getaddress)
64
64
  - [WIF](#wif)
65
65
  - [Script](#script)
66
66
  - [OutScript](#outscript)
67
+ - [Security](#security)
68
+ - [Supply chain security](#supply-chain-security)
69
+ - [License](#license)
67
70
 
68
71
  ## Payments
69
72
 
@@ -624,8 +627,14 @@ const hdkey = bip32.HDKey.fromExtendedKey(epriv, testnet.bip32);
624
627
  // const seed = 'cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T';
625
628
  const tx = new btc.Transaction();
626
629
  // A creator creating a PSBT for a transaction which creates the following outputs:
627
- tx.addOutput({ script: '0014d85c2b71d0060b09c9886aeb815e50991dda124d', amount: btc.Decimal.decode('1.49990000') });
628
- tx.addOutput({ script: '001400aea9a2e5f0f876a588df5546e8742d1d87008f', amount: btc.Decimal.decode('1.00000000') });
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
+ });
629
638
  // and spends the following inputs:
630
639
  tx.addInput({
631
640
  txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
@@ -696,8 +705,7 @@ tx2.updateOutput(1, {
696
705
  const psbt2 = tx2.toPSBT();
697
706
  // An updater which adds SIGHASH_ALL to the above PSBT must create this PSBT:
698
707
  const tx3 = btc.Transaction.fromPSBT(psbt2);
699
- for (let i = 0; i < tx3.inputs.length; i++)
700
- tx3.updateInput(i, { sighashType: btc.SigHash.ALL });
708
+ for (let i = 0; i < tx3.inputs.length; i++) tx3.updateInput(i, { sighashType: btc.SigHash.ALL });
701
709
  const psbt3 = tx3.toPSBT();
702
710
  /*
703
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:
@@ -734,6 +742,134 @@ deepStrictEqual(
734
742
  );
735
743
  ```
736
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
+
737
873
  ## Utils
738
874
 
739
875
  ### getAddress
@@ -844,6 +980,8 @@ The library has been independently audited:
844
980
  - [Changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/0.3.0..main).
845
981
  - The audit has been funded by [Ryan Shea](https://shea.io)
846
982
 
983
+ UTXO selection functionality has not been audited yet.
984
+
847
985
  ### Supply chain security
848
986
 
849
987
  1. **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures.