Giter Club home page Giter Club logo

fork--tapscript's Introduction

Tapscript Tools

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

Introduction

Tapscript uses some pretty cutting-edge stuff. If you are new to tapscript, please continue reading for a brief overview of how tapscript works. The library will make more sense if you have a general idea about what is happening under the hood.

If you already have a good understanding of tapscript, feel free to skip ahead by clicking here).

What is Tapscript?

Bitcoin uses a simple scripting language 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.

Tapscript 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.

Tapscript 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).

These new features came with the Taproot upgrade in 2019. Read more about it 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. We do this by hashing items together in pairs of two, repeatedly, until we are naturally left with one item (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 let us to recompute the root of the tree.

About Key Tweaking

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

Typically, we create a pair of signing keys by multiplying a secret number with a prime number called a "generator" (G). This process is done in a way that is computationally impossible to reverse without knowing the secret.

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    =    tweakedkey    = tweakedsec
pubkey + (tweak * G) = (tweakedkey * G) = tweakedpub

Later, we can choose to reveal the original public key and tweak, with a 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!

Tapscript uses key tweaking in order to lock coins to the root hash of our script tree. This provides us with two paths for spending coins:

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

You can also create tweaked keys using an internal pubkey that has a provably unknown secret key. This is useful for locking coins so that they cannot ever be spent with a tweaked key, and must be redeemed using a script!

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
  // Encode a pubkey or script hash into an address.
  encode : (key     : Bytes,  network ?: Networks) => string
  // Decode an address into a pubkey or script hash.
  decode : (address : string, network ?: Networks) => Buff
  // Return the scriptPubKey script for an address type.
  scriptPubKey : (key : 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 = Address.p2w.fromPubKey(pubkey, 'regtest')
// Address: bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk
const address = Address.p2w.encode(keyhash, 'regtest')
// Address: bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk
const bytes   = Address.p2w.decode(address)
// KeyHash: f44f76cbfd5b4c536d11d78b6700b8737c9eab07
const script  = Address.p2w.scriptPubKey(bytes)
// script: script: [ 'OP_0', 'f44f76cbfd5b4c536d11d78b6700b8737c9eab07' ]

Script Tool

This tool helps with parsing / serializing scripts.

Script = {
  // Encode a JSON formatted script into hex.
  encode : (script : ScriptData, varint = true) => string,
  // Decode a hex formatted script into JSON.
  decode : (script : string, varint = false)    => ScriptData
  // Normalize script / data to a particular format:
  fmt : {
    // Convert script to opcodes / hex data (asm format).
    toAsm()   => string[]  (asm format).
    // Convert script to bytes (script hex).
    toBytes() => Buff
     // Convert non-script witness data to bytes.
    toParam() => Buff  
  }
}

Signature Tool.

This tool helps with signatures and validation.

Signer.taproot = {
  // Calculate the signature hash for a transaction.
  hash : (
    txdata  : TxData | Bytes,
    index   : number,
    config  : HashConfig = {}
  ) => Uint8Array,
  // Sign a transaction using your *tweaked* private key.
  sign : (
    seckey  : Bytes,
    txdata  : TxData | Bytes,
    index   : number,
    config  : HashConfig = {}
  ) => Uint8Array,
  // Verify a transaction using the included tapkey (or specify a pubkey).
  verify : (
    txdata  : TxData | Bytes,
    index   : number,
    config  : HashConfig = {}
  ) => boolean
}

interface HashConfig {
  extension     ?: Bytes    // Hash and sign using this tapleaf.
  pubkey        ?: Bytes    // Verify using this pubkey instead of the tapkey.
  script        ?: Bytes    // Hash and sign using this script (for segwit spends).
  sigflag       ?: number   // Set the signature type flag.
  separator_pos ?: number   // If using OP_CODESEPARATOR, specify the latest opcode position.
  extflag       ?: number   // Set the extention version flag (future use).
  key_version   ?: number   // Set the key version flag (future use).
  throws        ?: boolean  // Should throw an exception on failure.
}

Example

Example of a basic pay-to-taproot key spend (similar to pay-to-pubkey):

// Sample secret / public key pair.
const seckey  = '730fff80e1413068a05b57d6a58261f07551163369787f349438ea38ca80fac6'
const pubkey  = '0307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba3'

// For key-spends, we need to tweak both the secret key and public key.
const [ tseckey ] = Tap.getSecKey(seckey)
const [ tpubkey ] = Tap.getPubKey(pubkey)

// Our taproot address is the encoded version of our public tapkey.
const address = Address.p2tr.encode(tpubkey, 'regtest')

// NOTE: For the next step, you need to send 100_000 sats to the above address.
// Make note of the txid of this transaction, plus the index of the output that
// you are spending.

const txdata = Tx.create({
  vin  : [{
    // The txid of your funding transaction.
    txid: 'fbde7872cc1aca4bc93ac9a923f14c3355b4216cac3f43b91663ede7a929471b',
    // The index of the output you are spending.
    vout: 0,
    // For Taproot, we need to specify this data when signing.
    prevout: {
      // The value of the output we are spending.
      value: 100000,
      // This is the script that our taproot address decodes into.
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    // We are locking up 99_000 sats (minus 1000 sats for fees.)
    value: 99000,
    // We are locking up funds to this address.
    scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')
  }]
})

// For this example, we are signing for input 0.

// Provide your tweaked secret key with the transaction, 
// plus the index # of the input you are signing for.
const sig = Signer.taproot.sign(tseckey, txdata, 0)

// Add your signature to the witness data for that input.
txdata.vin[0].witness = [ sig ]

// For verification, provided your 
await Signer.taproot.verify(txdata, 0, { throws: true })

console.log('Your address:', address)
console.log('Your txhex:', Tx.encode(txdata).hex)

You can find more examples in the main Examples section further down.

Note: There is also an identical Signer.segwit tool for signing and validating segwit (BIP0143) transactions. The segwit signer currently does not support the use of OP_CODESEAPRATOR. Any scripts containing this opcode will throw an exception by default.

Tap Tool

Tap = {
  // Returns the tweaked public key (and cblock) for a given tree (and target).
  getPubKey    : (pubkey : Bytes, config ?: TapConfig)  => TapKey,
  // Returns the tweaked secret key (and cblock) for a given tree (and target).
  getSecKey    : (seckey : Bytes, config ?: TapConfig)  => TapKey,
  // Converts a script into a tapleaf (for script-based spending).
  encodeScript : (script: ScriptData, version?: number) => string,
  // Checks the validity of a given leaf target and control block.
  checkPath    : (  
    tapkey : Bytes, 
    target : Bytes, 
    cblock : Bytes, 
    config ?: TapConfig  
  ) => boolean,  
  // Gives access to the various sub-tools (described below).
  tree  : TreeTool,
  tweak : TweakTool,
  util  : UtilTool
}

interface TapConfig {
  isPrivate ?: boolean
  target    ?: Bytes
  tree      ?: TapTree
  throws    ?: boolean
  version   ?: number
}

type TapKey = [
  tapkey : string,  // The tweaked public key.
  cblock : string   // The control block needed for spending the tapleaf target.
]

Examples

Example of tapping a key with no scripts (key-spend).

const [ tapkey ] = Tap.getPubKey(pubkey)

Example of tapping a key with a single script and returning a proof.

// Encode the script as bytes.
const bytes = Script.encode([ 'script' ])
// Convert the bytes into a tapleaf.
const target = Tap.tree.getLeaf(bytes)
// Provide the tapleaf as a target for generating the proof.
const [ tapkey, cblock ] = Tap.getPubKey(pubkey, { target })

Example of tapping a key with many scripts.

const scripts = [
  [ 'scripta' ],
  [ 'scriptb' ],
  [ 'scriptc' ]
]

// Convert the scripts into an array of tap leaves.
const tree = scripts
  .map(e => Script.encode(e))
  .map(e => Tap.tree.getLeaf(e))

// Optional: You can also add data to the tree.
const bytes = encodeData('some data')
const leaf  = Tap.tree.getLeaf(bytes)
tree.push(leaf)

// Select a target leaf for generating the proof.
const target = tree[0]

// Provide the tree and target leaf as arguments.
const [ tapkey, cblock ] = Tap.getPubKey(pubkey, { tree, target })

Tree Tool

This tool helps with creating a tree of scripts / data, plus the proofs to validate items in the tree.

Tap.tree = {
  // Returns a 'hashtag' used for padding. Mainly for internal use.
  getTag    : (tag : string) => Buff,
  // Returns a 'tapleaf' used for building a tree. 
  getLeaf   : (data : Bytes, version ?: number) => string,
  // Returns a 'branch' which combines two leaves (or branches).
  getBranch : (leafA : string, leafB : string) => string,
  // Returns the root hash of a tree.
  getRoot   : (leaves : TapTree) => Buff,
}

// A tree is an array of leaves, formatted as strings.
// These arrays can also be nested in multiple layers.
type TapTree = Array<string | string[]>

Tweak Tool

This tool helps with tweaking public / secret (private) keys.

Tap.tweak = {
  // Return a tweaked private key using the provided raw data.
  getSeckey   : (seckey: Bytes, data ?: Bytes | undefined) => Buff,
  // Return a tweaked public key using the provided raw data.
  getPubkey   : (pubkey: Bytes, data ?: Bytes | undefined) => Buff,
  // Return a 'taptweak' which is used for key tweaking.
  getTweak    : (key : Bytes, data ?: Bytes, isPrivate ?: boolean) => Buff,
  // Return a tweaked secret key using the provided tweak.
  tweakSeckey : (seckey: Bytes, tweak: Bytes) => Buff,
  // Return a tweaked public key using the provided tweak.
  tweakPubkey : (seckey: Bytes, tweak: Bytes) => Buff
}

Util Tool

This tool provides helper methods for reading and parsing data related to taproot.

Tap.util = {
  readCtrlBlock : (cblock : Bytes) => CtrlBlock,
  readParityBit : (parity ?: string | number) => number
}

interface CtrlBlock {
  version : number
  parity  : number
  intkey  : Buff
  paths   : string[]
}

Example

const cblock = 'c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27'
const { intkey, parity, paths, version } = Tap.util.readCtrlBlock(cblock)
// Expected output, with key formatted as hex instead of bytes (for readability).
{
  intkey: '187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27',
  parity: 3,
  paths: [],
  version: 192
}

Tx Tool

This tool helps with parsing / serializing transaction data.

Tx = {
  // Serializes a JSON transaction into a hex-encoded string.
  encode : (
    txdata       : TxData,  // The transaction JSON.
    omitWitness ?: boolean  // If you wish to omit the witness data.
  ) => string,
  // Parses a hex-encoded transaction into a JSON object.
  decode : (bytes : string | Uint8Array) => TxData,
  // Normalize transaction data to a particular format.
  fmt : {
    // Convert transaction data into JSON format.
    toJson  : (txdata ?: TxData | Bytes) => TxData,
    // Convert transaction data into a byte format.
    toBytes : (txdata ?: TxData | Bytes) => Buff
  },
  util : {
    // Get the transaction Id of a transaction.
    getTxid : (txdata : TxData | Bytes) => Buff,
    // Parse a scriptPubKey and get the type plus hash data.
    readScriptPubKey : (script : ScriptData) => ScriptPubKeyData,
    // Parse an array of witness data into named values.
    readWitness : (witness : ScriptData[])  => WitnessData
  }
}

interface TxData {
  version  ?: number           // The transaction verion. Defaults to version 2.
  vin       : InputData[]      // An array of transaction inputs.
  vout      : OutputData[]     // An array of transaction outputs.
  locktime ?: LockData         // The locktime of the transaction. Defautls to 0.
}

interface InputData {
  txid : string               // The txid of the UTXO being spent.
  vout : number               // The output index of the UTXO being spent.
  prevout   ?: OutputData     // The output data of the UTXO being spent.
  scriptSig ?: ScriptData     // The ScriptSig field (mostly deprecated).
  sequence  ?: SequenceData   // The sequence field for the input.
  witness   ?: ScriptData[]   // An array of witness data for the input.
}

interface OutputData {
  value : number | bigint     // The satoshi value of the output.
  scriptPubKey : ScriptData   // The locking script data.
}

export interface ScriptPubKeyData {
  type : OutputType
  data : Buff
}

interface WitnessData {
  annex  : Buff | null  // The annex data (if present) or null.
  cblock : Buff | null  // The control block (if present) or null.
  script : Buff | null  // The redeem script (if present) or null.
  params : Bytes[]      // Any remaining witness arguments.
}

type SequenceData = string | number
type LockData     = number
type ScriptData   = Bytes  | Word[]
type Word         = string | number | Uint8Array
type Bytes        = string | Uint8Array

Transaction Object

This is an example transaction in JSON format.

const txdata = {
  version: 2
  vin: [
    {
      txid: '1351f611fa0ae6124d0f55c625ae5c929ca09ae93f9e88656a4a82d160d99052',
      vout: 0,
      prevout: { 
        value: 10000,
        scriptPubkey: '512005a18fccd1909f3317e4dd7f11257e4428884902b1c7466c34e7f490e0e627da'
        
      },
      sequence: 0xfffffffd,
      witness: []
    }
  ],
  vout: [
    { 
      value: 9000, 
      address: 'bcrt1pqksclnx3jz0nx9lym4l3zft7gs5gsjgzk8r5vmp5ul6fpc8xyldqaxu8ys'
    }
  ],
  locktime: 0
}

Example Transactions

Here are a few partial examples to help demonstrate using the library. Check out the test/example/taproot directory to see a full implementation of each example.

Please feel free to contribute more!

Basic Pay-to-Pubkey Spending

Full example: keyspend.test.ts

// Create a keypair to use for testing.
const secret = 'ccd54b99acec77d0537b01431579baef998efac6b08e9564bc3047b20ec1bb4c'
const seckey = new SecretKey(secret, { type: 'taproot' })
const pubkey = seckey.pub

// For key spends, we need to get the tweaked versions
// of the secret key and public key.
const [ tseckey ] = Tap.getSecKey(seckey)
const [ tpubkey ] = Tap.getPubKey(pubkey)

// Optional: You could also derive the public key from the tweaked secret key.
const _tpubkey_example = new SecretKey(tseckey).pub.x.hex

// A taproot address is simply the tweaked public key, encoded in bech32 format.
const address = Address.p2tr.fromPubKey(tpubkey, 'regtest')

/* NOTE: To continue with this example, send 100_000 sats to the above address.
  You will also need to make a note of the txid and vout of that transaction,
  so that you can include that information below in the redeem tx.
*/ 

const txdata = Tx.create({
  vin  : [{
    // Use the txid of the funding transaction used to send the sats.
    txid: '1ec5b5403bbc7f26a5d3a3ee30d69166a19fa81b49928f010af38fa96986d472',
    // Specify the index value of the output that you are going to spend from.
    vout: 1,
    // Also include the value and script of that ouput.
    prevout: {
      // Feel free to change this if you sent a different amount.
      value: 100_000,
      // This is what our address looks like in script form.
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    // We are leaving behind 1000 sats as a fee to the miners.
    value: 99_000,
    // This is the new script that we are locking our funds to.
    scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')
  }]
})

// For this example, we are signing for input 0 of our transaction,
// using the tweaked secret key.
const sig = Signer.taproot.sign(tseckey, txdata, 0)

// Let's add this signature to our witness data for input 0.
txdata.vin[0].witness = [ sig ]

// Check if the signature and transaction are valid.
const isValid = await Signer.taproot.verify(txdata, 0)

Basic Pay-to-TapScript

Full example: tapscript.test.ts

const secret = '0a7d01d1c2e1592a02ea7671bb79ecd31d8d5e660b008f4b10e67787f4f24712'
const seckey = new SecretKey(secret, { type: 'taproot' })
const pubkey = seckey.pub

// Specify a basic script to use for testing.
const script = [ pubkey, 'OP_CHECKSIG' ]
const sbytes = Script.encode(script)

// For tapscript spends, we need to convert this script into a 'tapleaf'.
const tapleaf = Tap.tree.getLeaf(sbytes)

// Optional: There is a convenience method that converts scripts directly.
const _tapleaf = Tap.encodeScript(script)

// Generate a tapkey that includes our leaf script. Also, create a merlke proof 
// (cblock) that targets our leaf and proves its inclusion in the tapkey.
const [ tpubkey, cblock ] = Tap.getPubKey(pubkey, { target: tapleaf })

// A taproot address is simply the tweaked public key, encoded in bech32 format.
const address = Address.p2tr.fromPubKey(tpubkey, 'regtest')

/* NOTE: To continue with this example, send 100_000 sats to the above address.
  You will also need to make a note of the txid and vout of that transaction,
  so that you can include that information below in the redeem tx.
*/ 

const txdata = Tx.create({
  vin  : [{
    // Use the txid of the funding transaction used to send the sats.
    txid: '181508e3be1107372f1ffcbd52de87b2c3e7c8b2495f1bc25f8cf42c0ae167c2',
    // Specify the index value of the output that you are going to spend from.
    vout: 0,
    // Also include the value and script of that ouput.
    prevout: {
      // Feel free to change this if you sent a different amount.
      value: 100_000,
      // This is what our address looks like in script form.
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    // We are leaving behind 1000 sats as a fee to the miners.
    value: 99_000,
    // This is the new script that we are locking our funds to.
    scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')
  }]
})

// For this example, we are signing for input 0 of our transaction,
// using the untweaked secret key. We are also extending the signature 
// to include a commitment to the tapleaf script that we wish to use.
const sig = Signer.taproot.sign(seckey, txdata, 0, { extension: tapleaf })

// Add the signature to our witness data for input 0, along with the script
// and merkle proof (cblock) for the script.
txdata.vin[0].witness = [ sig.hex, script, cblock ]

// Check if the signature is valid for the provided public key, and that the
// transaction is also valid (the merkle proof will be validated as well).
const isValid = await Signer.taproot.verify(txdata, 0, { pubkey })

Create / Spend from a Tree of Scripts

Full example: taptree.test.ts

// Create a keypair to use for testing.
const secret = '0a7d01d1c2e1592a02ea7671bb79ecd31d8d5e660b008f4b10e67787f4f24712'
const seckey = new SecretKey(secret, { type: 'taproot' })
const pubkey = seckey.pub

// Specify an array of scripts to use for testing.
const scripts = [
  [ 1, 7, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 2, 6, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 3, 5, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 4, 4, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 5, 3, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 6, 2, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
  [ 7, 1, 'OP_ADD', 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ]
]

// Convert our array of scripts into tapleaves.
const tree = scripts.map(s => Tap.encodeScript(s))

// Pick one of our scripts as a target for spending.
const index  = Math.floor(Math.random() * 10) % 7
const script = scripts[index]
const target = Tap.encodeScript(script)

// Generate a tapkey that includes our tree. Also, create a merlke proof 
// (cblock) that targets our leaf and proves its inclusion in the tapkey.
const [ tpubkey, cblock ] = Tap.getPubKey(pubkey, { tree, target })

// A taproot address is simply the tweaked public key, encoded in bech32 format.
const address = Address.p2tr.fromPubKey(tpubkey, 'regtest')

/* NOTE: To continue with this example, send 100_000 sats to the above address.
 * You will also need to make a note of the txid and vout of that transaction,
 * so that you can include that information below in the redeem tx.
 */ 

const txdata = Tx.create({
  vin  : [{
    // Use the txid of the funding transaction used to send the sats.
    txid: 'e0b1b0aea95095bf7e113c37562a51cb8c3f50f5145c17952e766f7a84fcc5d7',
    // Specify the index value of the output that you are going to spend from.
    vout: 0,
    // Also include the value and script of that ouput.
    prevout: {
      // Feel free to change this if you sent a different amount.
      value: 100_000,
      // This is what our address looks like in script form.
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    // We are leaving behind 1000 sats as a fee to the miners.
    value: 99_000,
    // This is the new script that we are locking our funds to.
    scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')
  }]
})

// For this example, we are signing for input 0 of our transaction,
// using the untweaked secret key. We are also extending the signature 
// to include a commitment to the tapleaf script that we wish to use.
const sig = Signer.taproot.sign(seckey, txdata, 0, { extension: target })

// Add the signature to our witness data for input 0, along with the script
// and merkle proof (cblock) for the script.
txdata.vin[0].witness = [ sig.hex, script, cblock ]

// Check if the signature is valid for the provided public key, and that the
// transaction is also valid (the merkle proof will be validated as well).
const isValid = await Signer.taproot.verify(txdata, 0, { pubkey })

Create / Publish an Inscription

Creating an inscription is a three-step process:

  1. We create a script for publishing the inscription, and convert it into a bitcoin address.
  2. Send funds to the bitcoin address.
  3. Create a redeem transaction, which claims the previous funds (and publishes the data).

Full example: inscribe.test.ts

// The 'marker' bytes. Part of the ordinal inscription format.
const marker   = Buff.encode('ord')
/* Specify the media type of the file. Applications use this when rendering 
  * content. See: https://developer.mozilla.org/en-US/docs/Glossary/MIME_type 
  */
const mimetype = Buff.encode('image/png')
// Create a keypair to use for testing.
const secret = '0a7d01d1c2e1592a02ea7671bb79ecd31d8d5e660b008f4b10e67787f4f24712'
const seckey = new SecretKey(secret, { type: 'taproot' })
const pubkey = seckey.pub
// Basic format of an 'inscription' script.
const script  = [ pubkey, 'OP_CHECKSIG', 'OP_0', 'OP_IF', marker, '01', mimetype, 'OP_0', imgdata, 'OP_ENDIF' ]
// For tapscript spends, we need to convert this script into a 'tapleaf'.
const tapleaf = Tap.encodeScript(script)
// Generate a tapkey that includes our leaf script. Also, create a merlke proof 
// (cblock) that targets our leaf and proves its inclusion in the tapkey.
const [ tpubkey, cblock ] = Tap.getPubKey(pubkey, { target: tapleaf })
// A taproot address is simply the tweaked public key, encoded in bech32 format.
const address = Address.p2tr.fromPubKey(tpubkey, 'regtest')

/* NOTE: To continue with this example, send 100_000 sats to the above address.
 * You will also need to make a note of the txid and vout of that transaction,
 * so that you can include that information below in the redeem tx.
 */ 

const txdata = Tx.create({
  vin  : [{
    // Use the txid of the funding transaction used to send the sats.
    txid: 'b8ed81aca92cd85458966de90bc0ab03409a321758c09e46090988b783459a4d',
    // Specify the index value of the output that you are going to spend from.
    vout: 0,
    // Also include the value and script of that ouput.
    prevout: {
      // Feel free to change this if you sent a different amount.
      value: 100_000,
      // This is what our address looks like in script form.
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    // We are leaving behind 1000 sats as a fee to the miners.
    value: 99_000,
    // This is the new script that we are locking our funds to.
    scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')
  }]
})

// For this example, we are signing for input 0 of our transaction,
// using the untweaked secret key. We are also extending the signature 
// to include a commitment to the tapleaf script that we wish to use.
const sig = Signer.taproot.sign(seckey, txdata, 0, { extension: tapleaf })

// Add the signature to our witness data for input 0, along with the script
// and merkle proof (cblock) for the script.
txdata.vin[0].witness = [ sig, script, cblock ]

// Check if the signature is valid for the provided public key, and that the
// transaction is also valid (the merkle proof will be validated as well).
const isValid = await Signer.taproot.verify(txdata, 0, { pubkey, throws: true })

// You can publish your transaction data using 'sendrawtransaction' in Bitcoin Core, or you 
// can use an external API (such as https://mempool.space/docs/api/rest#post-transaction).

More examples to come!

Development / Testing

This library uses yarn for package management, tape for writing tests, and rollup for bundling cross-platform compatible code. Here are a few scripts that are useful for development.

## Compiles types and builds release candidates in /dist folder.
yarn build
## Run any typescript file using real-time compilation.
yarn start contrib/example.ts
## Runs all tests listed in test/tape.ts 
yarn test
## Full macro script for generating a new release candidate.
yarn release

Bugs / Issues

If you run into any bugs or have any questions, please submit an issue ticket.

Contribution

Feel free to fork and make contributions. Suggestions are welcome!

Future Roadmap

  • Add signature and validation for ecdsa (segwit and earlier).
  • Refactor and stress-test tree compilation with many (many) leaves.
  • Allow arbitrary ordering of tree elements.
  • Write more unit and vector tests (cover all the things).

Dependencies

This library contains minimal dependencies.

Buff-Utils
The swiss-army-knife of byte manipulation.
https://github.com/cmdruid/buff-utils

Crypto-Utils
User-friendly cryptography tools.
https://github.com/cmdruid/crypto-utils

Resources

BIP340 Wiki Page
This BIP covers schnorr signatures and verification.
https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki

BIP341 Wiki Page
This BIP covers the construction of trees, signature hashes, and proofs.
https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki

BIP342 Wiki Page
This BIP covers changes to opcodes and signature verification.
https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki

Tapscript example using Tap
This is a guide on how to use a command-line tool called btcdeb and Tap.
This tool will help you create a taproot transaction from scratch, which
is great for learning (and to debug any issues with this library :-)).
https://github.com/bitcoin-core/btcdeb/blob/master/doc/tapscript-example-with-tap.md

License

Use this library however you want!

Contact

You can find me on twitter at @btctechsupport or on nostr at npub1gg5uy8cpqx4u8wj9yvlpwm5ht757vudmrzn8y27lwunt5f2ytlusklulq3

fork--tapscript's People

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.