SkillAgentSearch skills...

Tapscript

A humble library for working with Tapscript and Bitcoin Transactions.

Install / Use

/learn @cmdruid/Tapscript
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Tapscript

A basic library for working with Taproot, Schnorr Signatures, and Bitcoin transactions.

Note: For nodejs users, please upgrade to version 19+ for globalThis support.

Introduction

Tapscript uses the latest feature upgrade to Bitcoin called Taproot. If you are new to Bitcoin or the Taproot upgrade, please continue reading for a brief overview of how it works. This library will be easier to follow if you know what taproot is doing under the hood.

If you already have a good understanding of Bitcoin and Taproot, feel free to skip ahead by clicking here.

What is Taproot?

Bitcoin uses a simple scripting language (called Bitcoin Script) that allows you to lock up coins into a contract. These contracts are published to the blockchain and enforced by all nodes in the network.

In order to settle a contract (and claim its coins), you are required to publish the entire contract, including parts that are not relevant to the settlement. This is expensive and wasteful, plus it leaks information that could have otherwise been kept private.

Taproot is a new way to publish these contracts to the blockchain that fixes the above concerns. It allows you to settle contracts by publishing only the portion of the contract that is relevant. This means smaller transactions, cheaper fees, and better privacy guarantees for the contract as a whole.

Taproot also comes with many other benefits, including:

  • It drastically simplifies the flow and logic of writing a contract.
  • You can create large, complex contracts that only need a small transaction to settle.
  • Commitments to data and other arbitrary things can be thrown into your contract for free.
  • The new schnorr-based signature format lets you do some crazy cool stuff (BIP340).

Read more about the Taproot upgrade in 2019 here.

How does Taproot work?

Taproot uses a simple trick involving something called a "merkle tree".

                hash(ab, cd)                  <- Final hash    (the root)
              /             \                
      hash(a, b)             hash(c, d)       <- Combined hash (the branches)
     /          \           /          \    
    hash(a) hash(b)        hash(c) hash(d)    <- Initial hash  (the leaves)
[ script(a), script(b), script(c), script(d) ]  

A merkle tree is simply a list of data that is reduced down into a single hash value. We do this by hashing values together in pairs of two, repeatedly, until we are naturally left with one value (the root).

The great thing about merkle trees is that you can use the root hash to prove that a piece of data (such as a script) was included somewhere in the tree, without having to reveal the entire tree.

For example, to prove that script(a) exists in the tree, we simply provide hash(b) and hash(c, d). This is all the information we need to recreate the root hash(ab, cd). We do not reveal any of the other scripts.

This allows us to break up a contract into many scripts, then lock coins to the root hash of our combined scripts. To redeem coins, we simply need to provide one of the scripts, plus a 'path' of hashes that lead us to the root hash of the tree.

About Key Tweaking

Another clever trick that Taproot uses, is something called "key tweaking".

In order to create a pair of keys used for signatures, we start with a secret number, or "key". We then multiply this key by a very large prime number, called a "generator" (G). This process is done in a way that is computationally impossible to reverse without knowing the secret. The resulting number then becomes our public key.

seckey * G => pubkey

We use a special set of numbers when making key-pairs, so that some arithmetic still works between the keys, without breaking their secret relationship with G. This is how we produce signatures and proofs.

seckey +    randomkey    +    msg    = signature      <= Does not reveal seckey.
pubkey + (randomkey * G) + (msg * G) = signature * G  <= Proves that seckey was used.

Key tweaking is just an extention of this. We use a piece of data to "tweak" both keys in our key-pair, then use the modified keys to sign and verify transactions.

seckey +    tweak    = tweaked_seckey
pubkey + (tweak * G) = tweaked_pubkey

Later, we can choose to reveal the original public key and tweak, as proof that both were used to construct the modified key. Or we can simply choose to sign using the modified key, and not reveal anything!

Taproot uses key tweaking in order to lock coins to our pubkey + root of our tree. This provides us with two paths for spending coins:

  • Using the tweaked pubkey (without revealing anything).
  • Using the interal pubkey + script + proof.

Note: the "proof" is the path of hashes we described earlier, which is needed to recompute the root hash.

If you want to eliminate the key-spending path (so that a script must be used to redeem funds), you can replace the pubkey with a random number. However, it is best to use a number that everyone can verify has an unknown secret key. One example of such a number is the following:

// BIP0341 specifies using the following pubkey value for script-only contracts.
// It is created by hashing the DER encoded coordinates of secp256k1 base point G:
'0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0'
// Since this pubkey was generated using a hash function, there is no feasible way 
// to compute what the matching private key would be.

Tool Index

This library provides a suite of tools for working with scripts, taproot, key tweaking, signatures and transactions. Use the links below to jump to the documentation for a certain tool.

Address Tool
Encode, decode, check, and convert various address types.
Script Tool
Encode scripts into hex, or decode into a script array.
Signer Tool
Produce signatures and validate signed transactions.
Tap Tool
Build, tweak, and validate trees of data / scripts.
Tx Tool
Encode transactions into hex, or decode into a JSON object.

About Buff

This library makes heavy use of the Buff tool for converting between data types. Buff is an extention of the Uint8Array type, so all Buff objects can naturally be treated as Uint8Array objects. Buff objects however incude an extensive API for converting into different types (for ex: buff.hex for hex strings). Please check the above link for more information on how to use Buff.

Import

Example import into a browser-based project:

<script src="https://unpkg.com/@cmdcode/tapscript"></script>
<script> const { Address, Script, Signer, Tap, Tx } = window.tapscript </script>

Example import into a commonjs project:

const { Address, Script, Signer, Tap, Tx } = require('@cmdcode/tapscript')

Example import into an ES module project:

import { Address, Script, Signer, Tap, Tx } from '@cmdcode/tapscript'

Address Tool

This tool allows you to encode, decode, check, an convert various address types.

Address = {
  // Work with Pay-to-Pubkey-Hash addresses (Base58 encoded).
  p2pkh  : => AddressTool,
  // Work with Pay-to-Script-Hash addresses (Base58 encoded).
  p2sh   : => AddressTool,
  // Work with Pay-to-Witness PubKey-Hash addresses (Bech32 encoded).
  p2wpkh : => AddressTool,
  // Work with Pay-to-Witness Script-Hash addresses (Bech32 encoded).
  p2wsh  : => AddressTool,
  // Work with Pay-to-Taproot addresses (Bech32m encoded).
  p2tr   : => AddressTool,
  // Decode any address format into a detailed object.
  decode   : (address : string) => AddressData,
  // Convert any address into its scriptPubKey format.
  toScriptPubKey : (address : string) => Buff
}

interface AddressTool {
  // Check if an address is valid.
  check  : (address : string, network ?: Networks) => boolean
  // Decode an address into a pubkey hash or script hash.
  decode : (address : string, network ?: Networks) => Buff
  // Convert a key or script into the proper hash.
  hash   : (input : Bytes | ScriptData) => Buff
  // Encode a pubkey hash or script hash into an address.
  encode : (input : Bytes,  network ?: Networks) => string
  // Return the scriptPubKey script for an address type.
  scriptPubKey : (input : string) => string[]
  // Return an address based on a public key (PKH type addresses only).
  fromPubKey : (pubkey : Bytes, network ?: Networks) => string
  // Return an address based on a script key (SH type addresses only).
  fromScript : (script : ScriptData, network ?: Networks) => string
}

interface AddressData {
  data    : Buff
  network : Networks
  prefix  : string
  script  : string[]
  type    : keyof AddressTools
}

type Networks = 'main' | 'testnet' | 'signet' | 'regtest'

Examples

Example of using the main Address API.

const address = 'bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk'
// You can decode any address, extract data, or convert to a scriptPubKey format.
const decoded = Address.decode(address)
// Example of the decoded data object.
{ 
  prefix  : 'bcrt1q', 
  type    : 'p2w', 
  network : 'regtest', 
  data    : 'f44f76cbfd5b4c536d11d78b6700b8737c9eab07',
  script  : [ 'OP_0', 'f44f76cbfd5b4c536d11d78b6700b8737c9eab07' ]
}
// You can also quickly convert between address and scriptPubKey formats.
const bytes = Address.toScriptPubKey(address)
// Bytes: 0014f44f76cbfd5b4c536d11d78b6700b8737c9eab07
const address = Address.fromScriptPubKey(scriptPubKey)
// Address : bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk

Example of using the AddressTool API for a given address type.

// Example 33-byte public key.
const pubkey  = '03d5af2a3e89cb72ff9ca1b36091ca46e4d4399abc5574b13d3e56bca6c0784679'
// You can encode / decode / convert keys and script hashes.
const address = A
View on GitHub
GitHub Stars213
CategoryDevelopment
Updated1mo ago
Forks50

Languages

TypeScript

Security Score

100/100

Audited on Feb 23, 2026

No findings