https://github.com/paulmillr/scure-btc-signer
Audited & minimal library for creating, signing & decoding Bitcoin transactions.
https://github.com/paulmillr/scure-btc-signer
bip174 bip327 bip340 bip341 bitcoin btc musig2 psbt schnorr signer taproot utxo
Last synced: 2 days ago
JSON representation
Audited & minimal library for creating, signing & decoding Bitcoin transactions.
- Host: GitHub
- URL: https://github.com/paulmillr/scure-btc-signer
- Owner: paulmillr
- License: mit
- Created: 2022-08-30T04:15:34.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-03-25T10:18:07.000Z (22 days ago)
- Last Synced: 2025-04-07T04:04:18.934Z (9 days ago)
- Topics: bip174, bip327, bip340, bip341, bitcoin, btc, musig2, psbt, schnorr, signer, taproot, utxo
- Language: JavaScript
- Homepage: https://paulmillr.com/noble/#scure
- Size: 1.07 MB
- Stars: 199
- Watchers: 9
- Forks: 50
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
- Funding: .github/funding.yml
- License: LICENSE
- Audit: audit/2023-02-21-cure53-audit-report.pdf
- Security: SECURITY.md
Awesome Lists containing this project
- awesome-bitcoin - scure-btc-signer
README
# scure-btc-signer
Audited & minimal library for creating, signing & decoding Bitcoin transactions.
- ๐ [**Audited**](#security) by an independent security firm
- โ๏ธ Create transactions, inputs, outputs, sign them
- ๐ก No network code: simplified audits and offline usage
- ๐ UTXO selection with different strategies
- ๐ป Classic & SegWit: P2PK, P2PKH, P2WPKH, P2SH, P2WSH, P2MS
- ๐งช Schnorr & Taproot BIP340/BIP341: P2TR, P2TR-NS, P2TR-MS
- ๐จ BIP174 PSBT, BIP327 MuSig2
- ๐ณ๏ธ Easy ordinals and inscriptions
- ๐ชถ 3300 linesInitial development has been funded by [Ryan Shea](https://shea.io).
Musig2 feature has been funded by [Arklabs](https://arklabs.to).For discussions, questions and support, visit
[GitHub Discussions](https://github.com/paulmillr/scure-btc-signer/discussions)
section of the repository._Check out all web3 utility libraries:_ [ETH](https://github.com/paulmillr/micro-eth-signer), [BTC](https://github.com/paulmillr/scure-btc-signer), [SOL](https://github.com/paulmillr/micro-sol-signer), [ordinals](https://github.com/paulmillr/micro-ordinals)
### This library belongs to _scure_
> **scure** โ audited micro-libraries.
- Zero or minimal dependencies
- Highly readable TypeScript / JS code
- PGP-signed releases and transparent NPM builds
- Check out [homepage](https://paulmillr.com/noble/#scure) & all libraries:
[base](https://github.com/paulmillr/scure-base),
[bip32](https://github.com/paulmillr/scure-bip32),
[bip39](https://github.com/paulmillr/scure-bip39),
[btc-signer](https://github.com/paulmillr/scure-btc-signer),
[starknet](https://github.com/paulmillr/scure-starknet)## Usage
> `npm install @scure/btc-signer`
> `deno add jsr:@scure/btc-signer`
> `deno doc jsr:@scure/btc-signer` # command-line documentation
We support all major platforms and runtimes.
For [Deno](https://deno.land), ensure to use [npm specifier](https://deno.land/[email protected]/node/npm_specifiers).
For React Native, you may need a [polyfill for crypto.getRandomValues](https://github.com/LinusU/react-native-get-random-values).```ts
import * as btc from '@scure/btc-signer';
```- [Payments](#payments)
- [P2PK Pay To Public Key](#p2pk-pay-to-public-key)
- [P2PKH Public Key Hash](#p2pkh-public-key-hash)
- [P2WPKH Witness Public Key Hash](#p2wpkh-witness-public-key-hash)
- [P2SH Script Hash](#p2sh-script-hash)
- [P2WSH Witness Script Hash](#p2wsh-witness-script-hash)
- [P2SH-P2WSH](#p2sh-p2wsh)
- [P2MS classic multisig](#p2ms-classic-multisig)
- [P2TR Taproot](#p2tr-taproot)
- [P2TR-NS Taproot multisig](#p2tr-ns-taproot-multisig)
- [P2TR-MS Taproot M-of-N multisig](#p2tr-ms-taproot-m-of-n-multisig)
- [P2TR-PK Taproot single P2PK script](#p2tr-pk-taproot-single-p2pk-script)
- [P2A Pay To Anchor](#p2a-pay-to-anchor)
- [Transaction](#transaction)
- [Encode/decode](#encodedecode)
- [Inputs](#inputs)
- [Outputs](#outputs)
- [Basic transaction sign](#basic-transaction-sign)
- [BIP174 PSBT multi-sig example](#bip174-psbt-multi-sig-example)
- [UTXO selection](#utxo-selection)
- [MuSig2](#musig2)
- [Ordinals and custom scripts](#ordinals-and-custom-scripts)
- [Utils](#utils)
- [getAddress](#getaddress)
- [WIF](#wif)
- [Script](#script)
- [OutScript](#outscript)
- [Bitcoin is flawed](#bitcoin-is-flawed)
- [Security](#security)
- [License](#license)## Payments
BTC has several UTXO types:
- P2PK: Legacy, from 2010
- P2PKH, P2SH, P2MS: Classic
- P2WPKH, P2WSH: classic, SegWit
- P2TR: Taproot, recommendedFor test examples, the usage is as following:
```sh
npm install @scure/btc-signer @scure/base assert
``````ts
import * as btc from '@scure/btc-signer';
import { hex } from '@scure/base';
import { deepStrictEqual, throws } from 'assert';
```### P2PK (Pay To Public Key)
Legacy script, doesn't have an address. Must be wrapped in P2SH / P2WSH / P2SH-P2WSH. Not recommended.
```ts
const uncompressed = hex.decode(
'04ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28'
);deepStrictEqual(btc.p2pk(uncompressed), {
type: 'pk',
script: hex.decode(
'4104ad90e5b6bc86b3ec7fac2c5fbda7423fc8ef0d58df594c773fa05e2c281b2bfe877677c668bd13603944e34f4818ee03cadd81a88542b8b4d5431264180e2c28ac'
),
});
```### P2PKH (Public Key Hash)
Classic (pre-SegWit) address.
```ts
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
deepStrictEqual(btc.p2pkh(PubKey), {
type: 'pkh',
address: '134D6gYy8DsR5m4416BnmgASuMBqKvogQh',
script: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
// P2SH-P2PKH
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
type: 'sh',
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
// P2WSH-P2PKH
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
type: 'wsh',
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
// P2SH-P2WSH-P2PKH
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
type: 'sh',
address: '3EHxWHyLv5Seu5Cd6D1cH56jLKxSi3ps8C',
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
```### P2WPKH (Witness Public Key Hash)
SegWit V0 version of [P2PKH](#p2pkh-public-key-hash). Basic bech32 address. Can't be wrapped in [P2WSH](#p2wsh-witness-script-hash).
```ts
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
deepStrictEqual(btc.p2wpkh(PubKey), {
type: 'wpkh',
address: 'bc1qz69ej270c3q9qvgt822t6pm3zdksk2x35j2jlm',
script: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
});
// P2SH-P2WPKH
deepStrictEqual(btc.p2sh(btc.p2wpkh(PubKey)), {
type: 'sh',
address: '3BCuRViGCTXmQjyJ9zjeRUYrdZTUa38zjC',
script: hex.decode('a91468602f2db7b7d7cdcd2639ab6bf7f5bfe828e53f87'),
redeemScript: hex.decode('0014168b992bcfc44050310b3a94bd0771136d0b28d1'),
});
```### P2SH (Script Hash)
Classic (pre-SegWit) script address. Useful for multisig and other advanced use-cases. Consumes full output of other payments โ NOT only script.
Required tx input fields to make it spendable: `redeemScript`
```ts
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
// Wrap P2PKH in P2SH
deepStrictEqual(btc.p2sh(btc.p2pkh(PubKey)), {
type: 'sh',
address: '3EPhLJ1FuR2noj6qrTs4YvepCvB6sbShoV',
script: hex.decode('a9148b530b962725af3bb7c818f197c619db3f71495087'),
redeemScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
```### P2WSH (Witness Script Hash)
SegWit V0 version of [P2SH](#p2sh-script-hash).
Required tx input fields to make it spendable: `witnessScript`
```ts
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
deepStrictEqual(btc.p2wsh(btc.p2pkh(PubKey)), {
type: 'wsh',
address: 'bc1qhxtthndg70cthfasy8y4qlk9h7r3006azn9md0fad5dg9hh76nkqaufnuz',
script: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
```### P2SH-P2WSH
Not really script type, but construction of P2WSH inside P2SH.
Required tx input fields to make it spendable: `redeemScript`, `witnessScript`
```ts
const PubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2pkh(PubKey))), {
type: 'sh',
address: '3EHxWHyLv5Seu5Cd6D1cH56jLKxSi3ps8C',
script: hex.decode('a9148a3d36fb710a9c7cae06cfcdf39792ff5773e8f187'),
redeemScript: hex.decode('0020b996bbcda8f3f0bba7b021c9507ec5bf8717bf5d14cbb6bd3d6d1a82defed4ec'),
witnessScript: hex.decode('76a914168b992bcfc44050310b3a94bd0771136d0b28d188ac'),
});
```### P2MS (classic multisig)
Classic / segwit (pre-taproot) M-of-N Multisig. Doesn't have an address, must be wrapped in P2SH / P2WSH / P2SH-P2WSH.
Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePubkeys` to override the behavior, for cases like `2-of-[A,A,B,C]`, which can be signed by `A or (B and C)`.
```ts
const PubKeys = [
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
];
// Multisig 2-of-3 wrapped in P2SH
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), {
type: 'sh',
address: '3G4AeQtzCLoDAyv2eb3UVTG5atfkyHtuRn',
script: hex.decode('a9149d91c6de4eacde72a7cc86bff98d1915b3c7818f87'),
redeemScript: hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
),
});
// Multisig 2-of-3 wrapped in P2WSH
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), {
type: 'wsh',
address: 'bc1qwnhzkn8wcyyrnfyfcp7555urssu5dq0rmnvg70hg02z3nxgg4f0qljmr2h',
script: hex.decode('002074ee2b4ceec10839a489c07d4a538384394681e3dcd88f3ee87a85199908aa5e'),
witnessScript: hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
),
});
// Multisig 2-of-3 wrapped in P2SH-P2WSH
deepStrictEqual(btc.p2sh(btc.p2wsh(btc.p2ms(2, PubKeys))), {
type: 'sh',
address: '3HKWSo57kmcJZ3h43pXS3m5UESR4wXcWTd',
script: hex.decode('a914ab70ab84b12b891364b4b2a14ca813cac308b24287'),
redeemScript: hex.decode('002074ee2b4ceec10839a489c07d4a538384394681e3dcd88f3ee87a85199908aa5e'),
witnessScript: hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
),
});
// Useful util: wraps P2MS in P2SH or P2WSH
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), btc.multisig(2, PubKeys));
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), btc.multisig(2, PubKeys, undefined, true));
// Sorted multisig (BIP67)
deepStrictEqual(btc.p2sh(btc.p2ms(2, PubKeys)), btc.sortedMultisig(2, PubKeys));
deepStrictEqual(btc.p2wsh(btc.p2ms(2, PubKeys)), btc.sortedMultisig(2, PubKeys, true));
```### P2TR (Taproot)
TapRoot (SegWit V1) script which replaces both public key and script types from previous versions.
Consumes `p2tr(PubKey?, ScriptTree?)` and works as `PubKey` OR `ScriptTree`, which means
if you use any spendable PubKey and ScriptTree of multi-sig, owner of private key for PubKey will
be able to spend output. If PubKey is undefined we use static unspendable PubKey by default, which leaks information about script type. However, any dynamic unspendable keys will require complex interaction
to sign multi-sig wallets, and there is no BIP/PSBT fields for that yet.Required tx input fields to make it spendable: `tapInternalKey`, `tapMerkleRoot`, `tapLeafScript`
```ts
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
// Key Path Spend (owned of private key for PubKey can spend)
deepStrictEqual(btc.p2tr(PubKey), {
type: 'tr',
address: 'bc1p7yu5dsly83jg5tkxcljsa30vnpdpl22wr6rty98t6x6p6ekz2gkqzf2t2s',
script: hex.decode('5120f13946c3e43c648a2ec6c7e50ec5ec985a1fa94e1e86b214ebd1b41d66c2522c'),
tweakedPubkey: hex.decode('f13946c3e43c648a2ec6c7e50ec5ec985a1fa94e1e86b214ebd1b41d66c2522c'),
tapInternalKey: hex.decode('0101010101010101010101010101010101010101010101010101010101010101'),
});const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
// Nested P2TR, owner of private key for any of PubKeys can spend whole
// By default P2TR expects binary tree, but btc.p2tr can build it if list of scripts passed.
// Also, you can include {weight: N} to scripts to create differently balanced tree.
deepStrictEqual(
clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey), btc.p2tr_pk(PubKey2), btc.p2tr_pk(PubKey3)])),
{
type: 'tr',
// weights for bitcoinjs-lib: [3,2,1]
address: 'bc1pj2uvajyygyu2zw0rg0d6yxdsc920kzc5pamfgtlqepe30za922cqjjmkta',
script: '512092b8cec8844138a139e343dba219b0c154fb0b140f76942fe0c873178ba552b0',
}
);
// If scriptsTree is already binary tree, it will be used as-is
deepStrictEqual(
clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey2), [btc.p2tr_pk(PubKey), btc.p2tr_pk(PubKey3)]])),
{
type: 'tr',
// default weights for bitcoinjs-lib
address: 'bc1pvue6sk9efyvcvpzzqkg8at4qy2u67zj7rj5sfsy573m7alxavqjqucc26a',
script: '51206733a858b9491986044205907eaea022b9af0a5e1ca904c094f477eefcdd6024',
}
);
```### P2TR-NS (Taproot multisig)
Taproot N-of-N multisig (`[ CHECKSIGVERIFY] CHECKSIG`).
First arg is M, if M!=PubKeys.length, it will create a multi-leaf M-of-N taproot script tree.
This allows one to reveal only `M` PubKeys on spend, without any information about the others.
This is fast for cases like 15-of-20, but extremely slow for cases like 5-of-20.Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePubkeys` to override the behavior, for cases like `2-of-[A,A,B,C]`, which can be signed by `A or (B and C)`.
```ts
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');// Simple 3-of-3 multisig
// Creates a single script that requires all three pubkeys: [PubKey, PubKey2, PubKey3]
deepStrictEqual(btc.p2tr_ns(3, [PubKey, PubKey2, PubKey3]), [
{
type: 'tr_ns',
script: hex.decode(
'200101010101010101010101010101010101010101010101010101010101010101ad200202020202020202020202020202020202020202020202020202020202020202ad201212121212121212121212121212121212121212121212121212121212121212ac'
),
},
]);
// Simple 2-of-3 multisig
// If M (pubkeys required) is less than N (# of pubkeys), then multiple scripts are created: [[PubKey, PubKey2], [PubKey, PubKey3], [PubKey2, PubKey3]]
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
deepStrictEqual(clean(btc.p2tr(undefined, btc.p2tr_ns(2, [PubKey, PubKey2, PubKey3]))), {
type: 'tr',
address: 'bc1pevfcmnkqqq09a4n0fs8c7mwlc6r4efqpvgyqpjvegllavgw235fq3kz7a0',
script: '5120cb138dcec0001e5ed66f4c0f8f6ddfc6875ca401620800c99947ffd621ca8d12',
});
```### P2TR-MS (Taproot M-of-N multisig)
M-of-N single leaf TapRoot multisig (` CHECKSIG [ CHECKSIGADD] NUMEQUAL`)
Duplicate public keys are not accepted to reduce mistakes. Use flag `allowSamePubkeys` to override the behavior, for cases like `2-of-[A,A,B,C]`, which can be signed by `A or (B and C)`.
**Experimental**, use at your own risk.
```ts
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
const PubKey2 = hex.decode('0202020202020202020202020202020202020202020202020202020202020202');
const PubKey3 = hex.decode('1212121212121212121212121212121212121212121212121212121212121212');
// 2-of-3 TapRoot multisig
deepStrictEqual(btc.p2tr_ms(2, [PubKey, PubKey2, PubKey3]), {
type: 'tr_ms',
script: hex.decode(
'200101010101010101010101010101010101010101010101010101010101010101ac200202020202020202020202020202020202020202020202020202020202020202ba201212121212121212121212121212121212121212121212121212121212121212ba529c'
),
});
// Creates a single script for [PubKey, PubKey2, PubKey3]
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
deepStrictEqual(clean(btc.p2tr(undefined, btc.p2tr_ms(2, [PubKey, PubKey2, PubKey3]))), {
type: 'tr',
address: 'bc1p6m2xevckax9zucumnnyvu4xhxem66ugc5r2zlw2a20s0hxnutl8qfef23s',
script: '5120d6d46cb316e98a2e639b9cc8ce54d73677ad7118a0d42fb95d53e0fb9a7c5fce',
});
```### P2TR-PK (Taproot single P2PK script)
Specific case of `p2tr_ns(1, [pubkey])`, which is the same as the BTC descriptor: `tr($H,pk(PUBKEY))`
```ts
const PubKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
// P2PK for taproot
const clean = (x) => ({ type: x.type, address: x.address, script: hex.encode(x.script) });
deepStrictEqual(clean(btc.p2tr(undefined, [btc.p2tr_pk(PubKey)])), {
type: 'tr',
address: 'bc1pfj6w68w3v2f4pkzesc9tsqfvy5znw5qgydwa832v3v83vjn76kdsmr4360',
script: '51204cb4ed1dd1629350d859860ab8012c2505375008235dd3c54c8b0f164a7ed59b',
});
```### P2A (Pay to Anchor)
Ephemeral anchors are supported. [Check out docs](https://bitcoinops.org/en/topics/ephemeral-anchors/).
```ts
const p2aScript = hex.decode('51024e73');
const decoded = btc.OutScript.decode(p2aScript);
deepStrictEqual(decoded, { type: 'p2a', script: p2aScript });
deepStrictEqual(hex.encode(btc.OutScript.encode(decoded)), '51024e73');
```## Transaction
### Encode/decode
We support both PSBTv0 and draft PSBTv2 (there is no PSBTv1). If PSBTv2 transaction is encoded into PSBTv1, all PSBTv2 fields will be stripped.
We strip 'unknown' keys inside PSBT, they needed for new version/features support,
however any unsupported feature/new version can significantly break assumptions about code.
If you have use-case where they are needed, create a github issue.PSBTv2 features tx_modifiable and taproot+bip32 are not supported yet.
```ts
// Decode
Transaction.fromRaw(raw: Bytes, opts: TxOpts = {}); // Raw tx
Transaction.fromPSBT(psbt: Bytes, opts: TxOpts = {}); // PSBT tx
// Encode
tx.unsignedTx; // Bytes of raw unsigned tx
tx.hex; // hex encoded signed raw tx
tx.toPSBT(ver = this.PSBTVersion); // PSBT
```### Inputs
We have txid (BE) instead of hash (LE) in transactions. We can support both,
but txid is consistent across block explorers, while some explorers treat hash
as txid - so hash is not consistent.Use `getInput` and `inputsLength` to read information about inputs: they return a copy.
This is necessary to avoid accidental modification of internal structures without calling methods (addInput/updateInput) that will verify correctness.```ts
type TransactionInput = {
txid?: Bytes,
index?: number,
nonWitnessUtxo?: ,
witnessUtxo?: {script?: Bytes; amount: bigint},
partialSig?: [Bytes, Bytes][]; // [PubKey, Signature]
sighashType?: P.U32LE,
redeemScript?: Bytes,
witnessScript?: Bytes,
bip32Derivation?: [Bytes, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
finalScriptSig?: Bytes,
finalScriptWitness?: Bytes[],
porCommitment?: Bytes,
sequence?: number,
requiredTimeLocktime?: number,
requiredHeightLocktime?: number,
tapKeySig?: Bytes,
tapScriptSig?: [Bytes, Bytes][]; // [PubKeySchnorr, LeafHash]
// [ControlBlock, ScriptWithVersion]
tapLeafScript?: [{version: number; internalKey: Bytes; merklePath: Bytes[]}, Bytes];
tapInternalKey?: Bytes,
tapMerkleRoot?: Bytes,
};tx.addInput(input: TransactionInput): number;
tx.updateInput(idx: number, input: TransactionInput);// Input
tx.addInput({ txid: new Uint8Array(32), index: 0 });
deepStrictEqual(tx.inputs[0], {
txid: new Uint8Array(32),
index: 0,
sequence: btc.DEFAULT_SEQUENCE,
});
// Update basic value
tx.updateInput(0, { index: 10 });
deepStrictEqual(tx.inputs[0], {
txid: new Uint8Array(32),
index: 10,
sequence: btc.DEFAULT_SEQUENCE,
});
// Add value as hex
tx.addInput({
txid: '0000000000000000000000000000000000000000000000000000000000000000',
index: 0,
});
deepStrictEqual(tx.inputs[2], {
txid: new Uint8Array(32),
index: 0,
sequence: btc.DEFAULT_SEQUENCE,
});
// Update key map
const pubKey = hex.decode('030000000000000000000000000000000000000000000000000000000000000001');
const bip1 = [pubKey, { fingerprint: 5, path: [1, 2, 3] }];
const pubKey2 = hex.decode('030000000000000000000000000000000000000000000000000000000000000002');
const bip2 = [pubKey2, { fingerprint: 6, path: [4, 5, 6] }];
const pubKey3 = hex.decode('030000000000000000000000000000000000000000000000000000000000000003');
const bip3 = [pubKey3, { fingerprint: 7, path: [7, 8, 9] }];
// Add K-V
tx.updateInput(0, { bip32Derivation: [bip1] });
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip1]);
// Add another K-V
tx.updateInput(0, { bip32Derivation: [bip2] });
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip1, bip2]);
// Delete K-V
tx.updateInput(0, { bip32Derivation: [[pubKey, undefined]] });
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2]);
// Second add of same k-v does nothing
tx.updateInput(0, { bip32Derivation: [bip2] });
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2]);
// Second add of k-v with different value breaks
throws(() => tx.updateInput(0, { bip32Derivation: [[pubKey2, bip1[1]]] }));
tx.updateInput(0, { bip32Derivation: [bip1, bip2, bip3] });
// Preserves order (re-ordered on PSBT encoding)
deepStrictEqual(tx.inputs[0].bip32Derivation, [bip2, bip1, bip3]);
// PSBT encoding re-order k-v
const tx2 = btc.Transaction.fromPSBT(tx.toPSBT());
deepStrictEqual(tx2.inputs[0].bip32Derivation, [bip1, bip2, bip3]);
// Remove field
tx.updateInput(0, { bip32Derivation: undefined });
deepStrictEqual(tx.inputs[0], {
txid: new Uint8Array(32),
index: 10,
sequence: btc.DEFAULT_SEQUENCE,
});// Read inputs
for (let i = 0; i < tx.inputsLength; i++) {
console.log('I', tx.getInput(i));
}
```### Outputs
`addOutputAddress` uses bigint amounts, which means satoshis - NOT btc. If you need btc representation, use Decimal:
```ts
const amountSatoshi = btc.Decimal.decode('1.5'); // 1.5 btc in satoshi
```Use `getOutput` and `outputsLength` to read outputs information. This methods returns copy of output, instead of internal representation.
This is necessary to avoid accidental modification of internal structures without calling methods (addOutput/updateOutput) that will verify correctness.```ts
type TransactionOutput = {
script?: Bytes,
amount?: bigint,
redeemScript?: Bytes,
witnessScript?: Bytes,
bip32Derivation?: [Bytes, {fingerprint: number; path: number[]}]; // [PubKey, DeriviationPath]
tapInternalKey?: Bytes,
};tx.addOutput(o: TransactionOutput): number;
tx.updateOutput(idx: number, output: TransactionOutput);
tx.addOutputAddress(address: string, amount: bigint, network = NETWORK): number;const compressed = hex.decode(
'030000000000000000000000000000000000000000000000000000000000000001'
);
const script = btc.p2pkh(compressed).script;
tx.addOutput({ script, amount: 100n });
deepStrictEqual(tx.outputs[0], {
script,
amount: 100n,
});
// Update basic value
tx.updateOutput(0, { amount: 200n });
deepStrictEqual(tx.outputs[0], {
script,
amount: 200n,
});
// Add K-V
tx.updateOutput(0, { bip32Derivation: [bip1] });
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip1]);
// Add another K-V
tx.updateOutput(0, { bip32Derivation: [bip2] });
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip1, bip2]);
// Delete K-V
tx.updateOutput(0, { bip32Derivation: [[pubKey, undefined]] });
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2]);
// Second add of same k-v does nothing
tx.updateOutput(0, { bip32Derivation: [bip2] });
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2]);
// Second add of k-v with different value breaks
throws(() => tx.updateOutput(0, { bip32Derivation: [[pubKey2, bip1[1]]] }));
tx.updateOutput(0, { bip32Derivation: [bip1, bip2, bip3] });
// Preserves order (re-ordered on PSBT encoding)
deepStrictEqual(tx.outputs[0].bip32Derivation, [bip2, bip1, bip3]);
// PSBT encoding re-order k-v
const tx3 = btc.Transaction.fromPSBT(tx.toPSBT());
deepStrictEqual(tx3.outputs[0].bip32Derivation, [bip1, bip2, bip3]);
// Remove field
tx.updateOutput(0, { bip32Derivation: undefined });
deepStrictEqual(tx.outputs[0], {
script,
amount: 200n,
});// Read outputs
for (let i = 0; i < tx.outputsLength; i++) {
console.log('O', tx.getOutput(i));
}
```### Basic transaction sign
```ts
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
const txP2WPKH = new btc.Transaction();
for (const inp of TX_TEST_INPUTS) {
txP2WPKH.addInput({
txid: inp.txid,
index: inp.index,
witnessUtxo: {
amount: inp.amount,
script: btc.p2wpkh(secp256k1.getPublicKey(privKey, true)).script,
},
});
}
for (const [address, amount] of TX_TEST_OUTPUTS) txP2WPKH.addOutputAddress(address, amount);
deepStrictEqual(hex.encode(txP2WPKH.unsignedTx), RAW_TX_HEX);
txP2WPKH.sign(privKey);
txP2WPKH.finalize();
deepStrictEqual(txP2WPKH.id, 'cbb94443b19861df0824914fa654212facc071854e0df6f7388b482a6394526d');
deepStrictEqual(
txP2WPKH.hex,
'010000000001033edaa6c4e0740ae334dbb5857dd8c6faf6ea5196760652ad7033ed9031c261c00000000000ffffffff0d9ae8a4191b3ba5a2b856c21af0f7a4feb97957ae80725ef38a933c906519a20000000000ffffffffc7a4a37d38c2b0de3d3b3e8d8e8a331977c12532fc2a4632df27a89c311ee2fa0000000000ffffffff03e8030000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac881300000000000017a914a860f76561c85551594c18eecceffaee8c4822d7876b24000000000000160014e8df018c7e326cc253faac7e46cdc51e68542c4202473044022024e7b1a6ae19a95c69c192745db09cc54385a80cc7684570cfbf2da84cbbfa0802205ad55efb2019a1aa6edc03cf243989ea428c4d216699cbae2cfaf3c26ddef5650121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f0247304402204415ef16f341e888ca2483b767b47fcf22977b6d673c3f7c6cae2f6b4bc2ac08022055be98747345b02a6f40edcc2f80390dcef4efe57b38c1bb7d16bdbca710abfd0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f02473044022069769fb5c97a7dd9401dbd3f6d32a38fe82bc8934c49c7c4cd3b39c6d120080c02202c181604203dc45c10e5290ded103195fae117d7fb0db19cdc411e73a76da6cb0121031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f00000000'
);
```### BIP174 PSBT multi-sig example
```ts
const testnet = {
wif: 0xef,
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
};
// The private keys in the tests below are derived from the following master private key:
const epriv =
'tprv8ZgxMBicQKsPd9TeAdPADNnSyH9SSUUbTVeFszDE23Ki6TBB5nCefAdHkK8Fm3qMQR6sHwA56zqRmKmxnHk37JkiFzvncDqoKmPWubu7hDF';
const hdkey = bip32.HDKey.fromExtendedKey(epriv, testnet.bip32);
// const seed = 'cUkG8i1RFfWGWy5ziR11zJ5V4U4W3viSFCfyJmZnvQaUsd1xuF3T';
const tx = new btc.Transaction();
// A creator creating a PSBT for a transaction which creates the following outputs:
tx.addOutput({
script: '0014d85c2b71d0060b09c9886aeb815e50991dda124d',
amount: btc.Decimal.decode('1.49990000'),
});
tx.addOutput({
script: '001400aea9a2e5f0f876a588df5546e8742d1d87008f',
amount: btc.Decimal.decode('1.00000000'),
});
// and spends the following inputs:
tx.addInput({
txid: '75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858',
index: 0,
});
tx.addInput({
txid: '1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83',
index: 1,
});
// must create this PSBT:
const psbt1 = tx.toPSBT();
// Given the above PSBT, an updater with only the following:
const tx2 = btc.Transaction.fromPSBT(psbt1);
tx2.updateInput(0, {
nonWitnessUtxo:
'0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000',
redeemScript:
'5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae',
bip32Derivation: [
[
'029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/0'") },
],
[
'02dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/1'") },
],
],
});
tx2.updateInput(1, {
// use witness utxo ({script, amount})
witnessUtxo: btc.RawTx.decode(
hex.decode(
'0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000'
)
).outputs[1],
redeemScript: '00208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903',
witnessScript:
'522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae',
bip32Derivation: [
[
'03089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/2'") },
],
[
'023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/3'") },
],
],
});
tx2.updateOutput(0, {
bip32Derivation: [
[
'03a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca58771',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/4'") },
],
],
});
tx2.updateOutput(1, {
bip32Derivation: [
[
'027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b50051096',
{ fingerprint: hdkey.fingerprint, path: btc.bip32Path("m/0'/0'/5'") },
],
],
});
// Must create this PSBT:
const psbt2 = tx2.toPSBT();
// An updater which adds SIGHASH_ALL to the above PSBT must create this PSBT:
const tx3 = btc.Transaction.fromPSBT(psbt2);
for (let i = 0; i < tx3.inputs.length; i++) tx3.updateInput(i, { sighashType: btc.SigHash.ALL });
const psbt3 = tx3.toPSBT();
/*
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:
- cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr (m/0'/0'/0')
- cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d (m/0'/0'/2')
*/
// We don't use HDKey, because it will everything because of bip32 derivation
const tx4 = btc.Transaction.fromPSBT(psbt3);
tx4.sign(btc.WIF(testnet).decode('cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr'));
tx4.sign(btc.WIF(testnet).decode('cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d'));
// must create this PSBT:
const psbt4 = tx4.toPSBT();
// Given the above updated PSBT, a signer with the following keys:
// cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au (m/0'/0'/1')
// cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE (m/0'/0'/3')
const tx5 = btc.Transaction.fromPSBT(psbt3);
tx5.sign(btc.WIF(testnet).decode('cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au'));
tx5.sign(btc.WIF(testnet).decode('cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE'));
// must create this PSBT:
const psbt5 = tx5.toPSBT();
// Given both of the above PSBTs, a combiner must create this PSBT:
const psbt6 = btc.PSBTCombine([psbt4, psbt5]);
// Given the above PSBT, an input finalizer must create this PSBT:
const tx7 = btc.Transaction.fromPSBT(psbt6);
tx7.finalize();
const psbt7 = tx7.toPSBT();
// Given the above PSBT, a transaction extractor must create this Bitcoin transaction:
const tx8 = btc.Transaction.fromPSBT(psbt7);
deepStrictEqual(
tx8.extract(),
hex.decode(
'0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000'
)
);
```### UTXO selection
UTXO selection is the process of choosing which UTXOs to use as inputs
when making an on-chain bitcoin payment. The library:- can create tx, integrated with the signer
- ensures change address is always specified
- supports bip69
- supports segwit + taproot
- calculates weight with good precision
- implements multiple strategiesTaproot estimation is precise, but you have to pass sighash if you want to use non-default one,
because it changes signature size. For complex taproot trees you need to filter tapLeafScript
to include only leafs which you can sign we estimate size with smallest leaf (same as finalization),
but in specific case keys for this leaf can be unavailable (complex multisig)`Oldest` / `Newest` expects UTXO provided in historical order (oldest first),
otherwise we have no way to detect age of tx.#### Strategies
Strategy selection is complicated. Best should be: `exactBiggest/accumSmallest`.
`exactBiggest/accumBiggest` creates tx with smallest fees,
but it breaks big outputs to small ones, which in the end will create
a lot of outputs close to dust.- `default`: good for privacy, same as `exactBiggest/accumBiggest`
- `all`: send all coins to change address (consolidation)
- `accum`: accumulates inputs until the target value (+fees) is reached, skipping detrimental inputs
- `exact`: accumulates inputs until the target value (+fees) is matched, does not accumulate inputs
that go over the target value (within a threshold)
- `accumNewest`
- `accumOldest`
- `accumSmallest`
- `accumBiggest`
- `exactNewest/accumNewest`
- `exactNewest/accumOldest`
- `exactNewest/accumSmallest`
- `exactNewest/accumBiggest`
- `exactOldest/accumNewest`
- `exactOldest/accumOldest`
- `exactOldest/accumSmallest`
- `exactOldest/accumBiggest`
- `exactSmallest/accumNewest`
- `exactSmallest/accumOldest`
- `exactSmallest/accumSmallest`
- `exactSmallest/accumBiggest`
- `exactBiggest/accumNewest`
- `exactBiggest/accumOldest`
- `exactBiggest/accumSmallest`
- `exactBiggest/accumBiggest`#### Example
```ts
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
const pubKey = secp256k1.getPublicKey(privKey, true);
const spend = btc.p2wpkh(pubKey, regtest);
const utxo = [
{
...spend, // add witness/redeem scripts from spend
// Get txid, index from explorer/network
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
index: 0,
// utxo tx information
// script can be used from spend itself or from explorer
witnessUtxo: { script: spend.script, amount: 100_000n }, // value in satoshi
},
{
...spend,
txid: hex.decode('0af50a00a22f74ece24c12cd667c290d3a35d48124a69f4082700589172a3aa2'),
index: 1,
witnessUtxo: { script: spend.script, amount: btc.Decimal.decode('1.5') }, // value in btc
},
// {
// ...spend,
// txid: hex.decode('75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858'),
// index: 0,
// // tx hex from blockchain (required for non-SegWit UTXO)
// nonWitnessUtxo: hex.decode(
// '0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000'
// ),
// },
];
const outputs = [
{ address: '2MvpbAgedBzJUBZWesDwdM7p3FEkBEwq3n3', amount: 50_000n }, // amount in satoshi
{
address: 'bcrt1pw53jtgez0wf69n06fchp0ctk48620zdscnrj8heh86wykp9mv20q7vd3gm',
amount: btc.Decimal.decode('0.5'), // amount in btc
},
];
// Send all utxo to specific address (consolidation):
// const selected = btc.selectUTXO(utxo, [], 'all', {
// changeAddress: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8', ...
const selected = btc.selectUTXO(utxo, outputs, 'default', {
changeAddress: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8', // required, address to send change
feePerByte: 2n, // require, fee per vbyte in satoshi
bip69: true, // lexicographical Indexing of Transaction Inputs and Outputs
createTx: true, // create tx with selected inputs/outputs
network: regtest,
});
// NOTE: 'selected' will 'undefined' if there is not enough funds
deepStrictEqual(selected.fee, 394n); // estimated fee
deepStrictEqual(selected.change, true); // change address used
deepStrictEqual(selected.outputs, [
{ address: '2MvpbAgedBzJUBZWesDwdM7p3FEkBEwq3n3', amount: 50000n },
{
address: 'bcrt1pw53jtgez0wf69n06fchp0ctk48620zdscnrj8heh86wykp9mv20q7vd3gm',
amount: 50_000_000n,
},
// Change address
// NOTE: with bip69 it is not necessarily last item in outputs
{
address: 'bcrt1pea3850rzre54e53eh7suwmrwc66un6nmu9npd7eqrhd6g4lh8uqsxcxln8',
amount: 99_949_606n,
},
]);
// No need to create tx manually!
const { tx } = selected;
tx.sign(privKey);
tx.finalize();
deepStrictEqual(tx.id, 'b702078d65edd65a84b2a97a669df5631b06f42a67b0d7090e540b02cc65aed5');
// real tx fee, can be bigger than estimated, since we expect signatures of maximal size
deepStrictEqual(tx.fee, 394n);
```## MuSig2
MuSig2 implementation conforming to [BIP-327](https://github.com/bitcoin/bips/blob/master/bip-0327.mediawiki)
is available in `@scure/btc-signer/musig2`. Check out [bip327-musig2.test.js](./test/bip327-musig2.test.js) as well:```ts
import * as musig2 from '@scure/btc-signer/musig2';
// MuSig2 Multi-signature for Alice, Bob, and Carol
// 1. Key Generation (for each signer: Alice, Bob, Carol)
// - Alice's key generation
const aliceSecretKey = randomBytes(32); // Alice generates a random 32-byte secret key
const alicePublicKey = musig2.IndividualPubkey(aliceSecretKey); // Alice derives her individual public key from her secret key
// - Bob's key generation
const bobSecretKey = randomBytes(32); // Bob generates a random 32-byte secret key
const bobPublicKey = musig2.IndividualPubkey(bobSecretKey); // Bob derives his individual public key from his secret key
// - Carol's key generation
const carolSecretKey = randomBytes(32); // Carol generates a random 32-byte secret key
const carolPublicKey = musig2.IndividualPubkey(carolSecretKey); // Carol derives her individual public key from her secret key// 2. Key Aggregation (All signers participate by sharing public keys)
const individualPublicKeys = [alicePublicKey, bobPublicKey, carolPublicKey]; // Collect all individual public keys
const sortedPublicKeys = musig2.sortKeys(individualPublicKeys); // Sort public keys lexicographically (as required by MuSig2)
const aggregatePublicKey = musig2.keyAggExport(musig2.keyAggregate(sortedPublicKeys)); // Extract the X-only aggregate public key (32 bytes)
// At this point, all signers have the 'aggregatePublicKey' and 'keyAggContext'.
// 3. Nonce Generation - Round 1 (Each signer generates and broadcasts public nonce)
const msg = new Uint8Array(32).fill(5); // Example message to be signed (32-byte message is recommended for BIP340)
// Alice generates her nonce
const aliceNonces = musig2.nonceGen(alicePublicKey, aliceSecretKey, aggregatePublicKey, msg);
// Secret nonce: must be kept secret and used only once per signing session!
// Public nonce: to be shared with Bob and Carol
// Bob generates his nonce
const bobNonces = musig2.nonceGen(bobPublicKey, bobSecretKey, aggregatePublicKey, msg);
// Carol generates her nonce
const carolNonces = musig2.nonceGen(carolPublicKey, carolSecretKey, aggregatePublicKey, msg);
// Each signer creates own instance
const session = new musig2.Session(
// 4. Nonce Aggregation (All signers participate by sharing public nonces)
musig2.nonceAggregate([aliceNonces.public, bobNonces.public, carolNonces.public]),
sortedPublicKeys,
msg
);
// At this point, all signers have the 'aggregateNonce'.
// 5. Partial Signature Generation - Round 2 (Each signer generates partial signature)
// Alice generates her partial signature
const alicePartialSignature = session.sign(aliceNonces.secret, aliceSecretKey);
// Bob generates his partial signature
const bobPartialSignature = session.sign(bobNonces.secret, bobSecretKey);
// Carol generates her partial signature
const carolPartialSignature = session.sign(carolNonces.secret, carolSecretKey);
// 6. Partial Signature Aggregation (Anyone can aggregate partial signatures)
const partialSignatures = [alicePartialSignature, bobPartialSignature, carolPartialSignature]; // Collect all partial signatures
const finalSignature = session.partialSigAgg(partialSignatures); // Aggregate partial signatures to create the final signature// 7. Signature Verification (Anyone can verify the final signature)
// Verify the final signature
import { schnorr } from '@noble/curves/secp256k1';
schnorr.verify(finalSignature, msg, aggregatePublicKey);
```## Ordinals and custom scripts
We support custom scripts. You can pass it as last argument to `p2tr`.
We've developed separate [micro-ordinals](https://github.com/paulmillr/micro-ordinals) package, which contains:
- Real code for ordinals / inscriptions / runes
- CLI tool that allows to upload files as inscriptions
- Example usage of custom scripts## Utils
### secp256k1 keys
```ts
import { pubSchnorr, signSchnorr } from '@scure/btc-signer/utils';
import { pubECDSA, signECDSA } from '@scure/btc-signer/utils';
import { randomPrivateKeyBytes } from '@scure/btc-signer/utils';const priv = randomPrivateKeyBytes();
const pub = pubSchnorr(priv);
```### getAddress
Returns common addresses from privateKey
```ts
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
deepStrictEqual(btc.getAddress('pkh', privKey), '1C6Rc3w25VHud3dLDamutaqfKWqhrLRTaD'); // P2PKH (legacy address)
deepStrictEqual(btc.getAddress('wpkh', privKey), 'bc1q0xcqpzrky6eff2g52qdye53xkk9jxkvrh6yhyw'); // SegWit V0 address
deepStrictEqual(
btc.getAddress('tr', priv),
'bc1p33wm0auhr9kkahzd6l0kqj85af4cswn276hsxg6zpz85xe2r0y8syx4e5t'
); // TapRoot KeyPathSpend
```#### WIF
Encoding/decoding of WIF privateKeys. Only compressed keys are supported for now.
```ts
const privKey = hex.decode('0101010101010101010101010101010101010101010101010101010101010101');
deepStrictEqual(btc.WIF().encode(privKey), 'KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH');
deepStrictEqual(
hex.encode(btc.WIF().decode('KwFfNUhSDaASSAwtG7ssQM1uVX8RgX5GHWnnLfhfiQDigjioWXHH')),
'0101010101010101010101010101010101010101010101010101010101010101'
);
```### Script
Encoding/decoding bitcoin scripts
```ts
deepStrictEqual(
btc.Script.decode(
hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
)
).map((i) => (P.isBytes(i) ? hex.encode(i) : i)),
[
'OP_2',
'030000000000000000000000000000000000000000000000000000000000000001',
'030000000000000000000000000000000000000000000000000000000000000002',
'030000000000000000000000000000000000000000000000000000000000000003',
'OP_3',
'CHECKMULTISIG',
]
);
deepStrictEqual(
hex.encode(
btc.Script.encode([
'OP_2',
hex.decode('030000000000000000000000000000000000000000000000000000000000000001'),
hex.decode('030000000000000000000000000000000000000000000000000000000000000002'),
hex.decode('030000000000000000000000000000000000000000000000000000000000000003'),
'OP_3',
'CHECKMULTISIG',
])
),
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
);
```### OutScript
Encoding / decoding of output scripts
```ts
deepStrictEqual(
btc.OutScript.decode(
hex.decode(
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
)
),
{
type: 'ms',
m: 2,
pubkeys: [
'030000000000000000000000000000000000000000000000000000000000000001',
'030000000000000000000000000000000000000000000000000000000000000002',
'030000000000000000000000000000000000000000000000000000000000000003',
].map(hex.decode),
}
);
deepStrictEqual(
hex.encode(
btc.OutScript.encode({
type: 'ms',
m: 2,
pubkeys: [
'030000000000000000000000000000000000000000000000000000000000000001',
'030000000000000000000000000000000000000000000000000000000000000002',
'030000000000000000000000000000000000000000000000000000000000000003',
].map(hex.decode),
})
),
'5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae'
);
```## Bitcoin is flawed
Bitcoin is more complex than ETH / SOL despite having less features:
- **Legacy:** too much decade-old code / standards
- **Overengineering:** features were designed to be extensible and future-proof; then abandoned when future arrived.
- Transaction has `version` field, which is nice, allowing to change format later, however when there
was first change (SegWit V0), instead of using different tx version, there was hack with zero-inputs prefix
(tx cannot have zero inputs, so inputsCount=0 + '01' flag after that for tx version with witness data).
Probably it was done so it won't interfere with different transaction versions of different coins.
However, there is also `txVersion=2` (BIP68) which changes lockTime behaviour, but not tx format.
- There is bech32/witness program addresses, which is very extensible. By default rules software should
support future versions (0..16), so there will be no change in libraries to support new addresses.
However, even after first update (version 0 to 1) format of addresses itself changed from bech32 to bech32m,
so whole mechanic of different address versions is already unused. Also, supporting future version
of addresses is cool, but they are currently unspendable and we cannot know rules for spending new version address,
which means any new version address created now will be unspendable in future (SegWitV0->SegWitV1(taproot) even
changes public key format). So we cannot use this whole mechanic of future addresses at all (or users can accidentally create unspendable address and lose coins).
- PSBT supports unknown fields, according to spec we need to pass them as is, without modifications.
It was probably done this way to be future-proof: new version of PSBT can be parsed with old parser, which will ignore new fields. However, when future came (PSBTv2), format significantly changed (no `global.unsignedTx`) anyway.
- **Bad BIP specs:** likely because there is only one relevant BTC Core implementation.
- To implement something, specs are not enough: need to read source code of Core (which is very complex, especially functional tests which re-implement parts in python) and other bitcoin libraries (such as bitcoinjs-lib)
- No one cares about specs much: for example, in [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki),
despite being old 2017 spec, formatting issues still haven't been fixed: `m/0'/0'/0'`
- [BIP174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) multisig example has unsorted partialSig field
inside input (it's better to sort it)
- Many PSBT tests are in format of `valid/invalid` (especially for PSBTv2), which isn't very helpful.
Parser which produces garbage, but doesn't throw exception, is still broken.
- **Major flaws in PSBT:**
- PSBT was designed to be opaque key-value store. Combiner only needed to merge dictories, without a need to understand
fields. However, parsing requires to understand `unsignedTx`, since there is no input/output count in format itself.
- Instead of `global, inputCount, inputs[], outputCount, outputs[]`, inputCount and outputCount are stored inside
`global.unsignedTx` (v0) or `global.inputCount/global.outputCount` (v2), which means in order to parse
basic structure we need to completely parse global KV and understand its fields.
- **Security issue:** Unknown PSBT fields can be used to pass some code to backdoored wallets while being ignore by others.
- In JS, it is even harder to implement properly, because JS doesn't support complex keys in objects / dicts.
There is whole controlBlock of taproot in key inside tapLeafScript, which we need to parse, leading to structure like:
`{key: {version, internalKey, merklePath}, value: {script, version}`.
But, since there is no support for complex keys, we cannot do `correct by construction` using js objects,
we need to do this dict as array and constantly check if keys are unique.## Security
The library has been independently audited:
- at version 0.3.0, in Feb 2023, by [cure53](https://cure53.de)
- PDFs: [online](https://cure53.de/audit-report_micro-btc-signer.pdf), [offline](./audit/2023-02-21-cure53-audit-report.pdf)
- [Changes since audit](https://github.com/paulmillr/scure-btc-signer/compare/0.3.0..main).
- The audit has been funded by [Ryan Shea](https://shea.io)MuSig2 and UTXO selection has not been audited yet.
Commit [58d4554](58d455480919e968aabff132503560effb2f8eaf)
split the library from one into several files to ease future maintainability.If you see anything unusual: investigate and report.
### Supply chain security
- **Commits** are signed with PGP keys, to prevent forgery. Make sure to verify commit signatures
- **Releases** are transparent and built on GitHub CI. Make sure to verify [provenance](https://docs.npmjs.com/generating-provenance-statements) logs
- **Rare releasing** is followed to ensure less re-audit need for end-users
- **Dependencies** are minimized and locked-down: any dependency could get hacked and users will be downloading malware with every install.
- We make sure to use as few dependencies as possible
- Automatic dep updates are prevented by locking-down version ranges; diffs are checked with `npm-diff`
- **Dev Dependencies** are disabled for end-users; they are only used to develop / build the source codeFor this package, there are 4 dependencies; and a few dev dependencies:
- [noble-hashes](https://github.com/paulmillr/noble-hashes) provides cryptographic hashing functionality
- [noble-curves](https://github.com/paulmillr/noble-curves) provides secp256k1 elliptic curve
- [scure-base](https://github.com/paulmillr/scure-base) provides base58 and bech32
- [micro-packed](https://github.com/paulmillr/micro-packed) is responsible for binary encoding
- micro-bmark, micro-should and jsbt are used for benchmarking / testing / build tooling and developed by the same author
- 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## Contributing & testing
- `npm install && npm run build && npm test` will build the code and run tests.
- `npm run lint` / `npm run format` will run linter / fix linter issues.
- `npm run build:release` will build single file## Learning & documentation
There are several nice resources on the topic:
- [Learn me a bitcoin](https://learnmeabitcoin.com)
- [Bitcoin wiki](https://bitcoinwiki.org)## License
MIT (c) Paul Miller [(https://paulmillr.com)](https://paulmillr.com), see LICENSE file.