Code Monkey home page Code Monkey logo

tapscript's Introduction

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 = Address.p2wpkh.fromPubKey(pubkey, 'regtest')
// Address: bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk
const address = Address.p2wpkh.encode(keyhash, 'regtest')
// Address: bcrt1q738hdjlatdx9xmg3679kwq9cwd7fa2c84my9zk
const bytes   = Address.p2wpkh.decode(address)
// KeyHash: f44f76cbfd5b4c536d11d78b6700b8737c9eab07
const script  = Address.p2wpkh.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  
  }
}

Signer 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 = {
  // Create a transaction object from partial JSON. 
  // Any missing fields will be repalced with default values.
  create : (data : Partial<TxData>) => TxData,
  // Serialize 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,
  // Parse 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,
    // Get the size data of a transaction.
    getTxSize : (txdata : TxData | Bytes) => TxSizeData,
    // 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.
}

interface TxSizeData {
  size   : number       // Size of the transaction in bytes.
  bsize  : number       // Base size of the tx (without witness data).
  vsize  : number       // Size of the tx with witness discount applied.
  weight : number       // Used to calculate the vsize of the tx.
}

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

tapscript's People

Contributors

cmdruid avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

tapscript's Issues

`Tx.encode` fail for file larger than 260 byte

I have tried to inscribe file has size < 260b successfully according to the example, but for files larger than 260b it seems that encoding tx is having problems. it seems the 520 byte limit is misdefined leading to the wrong encoding (personally)

How to use sequence in input?

here is my code

    const txdata = Tx.create({
        vin: [{
            txid: '5b8ce54ca0993713552939b3d248c1f1b98c3ba18575f0d34f6b5ae13d58eac0',
            vout: 0,
            prevout: {
                value: 5000,
                scriptPubKey: ['OP_1', tpubkey]
            }
        }],
        vout: [
            {
                value: 4000,
                scriptPubKey: Address.toScriptPubKey(receiverAddress)
            }
        ]
    })
    console.log(txdata)
    const sig = Signer.taproot.sign(senderSeckey, txdata, 0, { extension: lockTapleaf })
    txdata.vin[0].witness = [sig.hex, puzzle.toString('hex'), lockScript, cblock]

then, I want to set sequence in input like this

const txdata = Tx.create({
        vin: [{
            txid: '5b8ce54ca0993713552939b3d248c1f1b98c3ba18575f0d34f6b5ae13d58eac0',
            vout: 0,
            prevout: {
                value: 5000,
                scriptPubKey: ['OP_1', tpubkey]
            },
            sequence: 6,
        }],
        vout: [
            {
                value: 4000,
                scriptPubKey: Address.toScriptPubKey(receiverAddress)
            }
        ]
    })

But I always got error "Transaction has superfluous witness data", what should I do?

OP_RETURN in output

@cmdruid was trying to implement the runes specification on my existing codebase used for inscriptions.

I feel like this should be a straightforward task but for whatever reason once my txn is broadcast the block explorers show UNKNOWN rather than the OP_RETURN. runes protocol aside, i should be able to use an OP_RETURN with any data following it, right?

Here is some sample code

var payload = hexToBytes( '148dde9d011427' );
var rune   = [ 'OP_RETURN', 'OP_13', payload ]
var runestone = Script.encode(rune);

i then add runestone as the scriptpubkey of a 0 value txn output. the txn signs and broadcasts no problem.

here are some examples on testnet that shown UNKNOWN

It should look something like the OP_RETURN here.

any thoughts appreciated!

Broadcast error, Public key version reserved for soft-fork upgrades

follow inscribe.test.ts, broadcast tx to the mainnet, get error!

const txdata = Tx.create({
  vin  : [{
    txid: '30fcb229bdef4ab1a9d864e2cb4b562c99b8ea00d68ff156f31ec1468ba6d893',
    vout: 0,
    prevout: {
      value: 4_000,
      scriptPubKey: [ 'OP_1', tpubkey ]
    },
  }],
  vout : [{
    value: 1_000,
    scriptPubKey: Address.toScriptPubKey('bc1p93mun355nyke02gvdhx7vmes5l8enh2yw86r8x0fl98hwlug2dvq3xqj66')
  }]
})

const sig = Signer.taproot.sign(seckey, txdata, 0, { extension: tapleaf })
txdata.vin[0].witness = [ sig, script, cblock ]

const response = await axios.post('https://blockstream.info/api/tx', Tx.encode(txdata).hex)
console.log(response.data)
print:
sendrawtransaction RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Public key version reserved for soft-fork upgrades)"}

Runes

Will this this work for Runes also or do you have to change up a lot to make it work for Runes?

ReferenceError: crypto is not defined

I got this error:

C:\path\to\my\project\node_modules\@cmdcode\tapscript\node_modules\@cmdcode\buff-utils\dist\module.mjs:353
const { getRandomValues } = crypto ?? globalThis.crypto ?? window.crypto;
                            ^
ReferenceError: crypto is not defined
    at Object.<anonymous> (C:\path\node_modules\@cmdcode\tapscript\node_modules\@cmdcode\buff-utils\dist\module.mjs:353:29)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Object.require.extensions.<computed> [as .js] (C:\path\to\AppData\Local\npm-cache\_npx\<random looking num here>\node_modules\ts-node\src\index.ts:1608:43)       
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Function.Module._load (node:internal/modules/cjs/loader:958:12)
    at Module.require (node:internal/modules/cjs/loader:1141:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Object.<anonymous> (C:\path\to\my\project\index.ts:1:1)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)

When I try to run my script that looks like this "index.ts.":

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

function main() {
    const address = 'address here'
    const decoded = Address.decode(address)
    console.log(decoded)
}

main()

crypto is not defined

I keep getting this error
I dont want to use patch-package (by making changes to the file)

Is there any other easier way to solve this error?
Please do reply fast!!

image

A test failure

Just a small problem, in "inscribe.test.ts", L21, new URL() failed in the windowOS, and it should use absolute URL

Sign problem

Error: crypto.getRandomValues must be defined
    at randomBytes$2 (/Users/sonpin/Documents/bitcoin/new/node_modules/@cmdcode/tapscript/dist/main.cjs:106:11)
    at Function.random (/Users/sonpin/Documents/bitcoin/new/node_modules/@cmdcode/tapscript/dist/main.cjs:1480:25)
    at sign (/Users/sonpin/Documents/bitcoin/new/node_modules/@cmdcode/tapscript/dist/main.cjs:7661:46)
    at Object.signTx [as sign] (/Users/sonpin/Documents/bitcoin/new/node_modules/@cmdcode/tapscript/dist/main.cjs:7656:17)

I've got this issue when sign

Signer.taproot.sign(seckey, txdata, 0, { extension: tapleaf })

Error: Size greater than stream: 38133322 > 56518

I like this library a lot, but I have hit a blocker.
Tx.decode() fails with this error for some transactions that have already been confirmed.
The error message is always the same: 38133322 > 56518 for the txn in my repro, below.

I notice this tends to happen with txns with many inputs, or many outputs.

Here's my repro script (node 19.9.0):

// Version 1.4.4 from npm.
import { Tx } from "@cmdcode/tapscript";
import * as lib_bitcoin_rpc from './lib_bitcoin_rpc.js'

async function main() {
  // The following txid causes a decode script crash.
  const txid = '74424af880bad9a82b0969b241fc8ee15db56ef5b852fc81649a7545a1e27036'
  // The following txid does not cause a crash.
  //const txid = '1cd87feb39f3696a0573cdfc5b981f6040954817fb1d59a082fec749693625b9'

  // Get txn as hexstring.
  const { result: rawTxn } = await lib_bitcoin_rpc.getRawTransaction(txid)
  console.log(rawTxn)

  const tx = Tx.decode(rawTxn)
  console.log('Done.')
}

main()

Output:

020000000001fd24021615950c0d6e983937ca8cf33e8b94ec90b51b67868e66e1626170f942b2b2e80500000000fdffffff374f40f1b0b08e7f7ff144a5266c7e9ecdc3b8d30a7afec3ebd1b0e927ad3f812200000000fdffffff94090c725875db7194e1f47b2f1f1f8ef9c3c4aea3ea487e1c8b66fbadbfbaa60300000000fdfffffffb21f9f7c8b3028afac209af3c7d14e57e6e7dd3b45a452b37324892c71da8100600000000fdffffff8234d40bdb83594b8f9f6bc0f3889007f0ebfaea46bd38fb47de59a85821642b0100000000fdffffff60811e1de61a40b4ad98b55562461d5f380163d8682a1c39
...
c68c914817c641f4a29ae40121023e043fd3e13df9c2a8640adc0d07842fb490f93c5f307ce7d859ed277b83e14f16ae0c00
file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:1069
            throw new Error(`Size greater than stream: ${size} > ${this.size}`);
                  ^

Error: Size greater than stream: 38133322 > 56518
    at Stream.peek (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:1069:19)
    at Stream.read (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:1075:28)
    at readData (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:6684:18)
    at readScript (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:6688:18)
    at readInput (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:6651:20)
    at readInputs (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:6643:21)
    at Object.decodeTx [as decode] (file:///home/donn/workspace/gitlab.com/proj/inscribe/node_modules/@cmdcode/tapscript/dist/module.mjs:6613:17)
    at main (file:///home/donn/workspace/gitlab.com/proj/inscribe/hello_repro_decode_txn_error_for_publishing_to_issue.js:18:17)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Node.js v19.9.0

I hope this can be investigated because I would hate to port all my code to another lib.
Please let me know if there's more info I can provide.
Thank you

witness problem

I performed a sats token mint transaction using the go-ord-tx repository without much knowledge. I did not know that the mempool limit was 25. My transaction was approved, but the script did not execute after 25 transactions. How can I make the utxos at the tahhahut address execute the script using this library? I'm having trouble creating the 1st and 3rd witness values. I'm just starting to learn. Can you help me?

ReferenceError: crypto is not defined

ReferenceError: crypto is not defined
at Object. (node_modules/.store/@cmdcode[email protected]/node_modules/@cmdcode/tapscript/dist/main.cjs:339:48)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Module._load (node:internal/modules/cjs/loader:827:12)
at Module.require (node:internal/modules/cjs/loader:1005:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object. (/Users/JunK/messi/king_chain/temp_typscript/index.js:2:46)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)

Ordinal Provenance

@cmdruid appreciate all the work and examples in this library!

I had a question regarding ordinal provenance as documented here.

I have an inscription flow in place which generally follows the example you provided for inscriptions. While I believe i have followed the ordinal provenance documentation correctly as far as the script construction and input values, I am having issues signing the transaction.

Code snippets below, any thoughts/suggestions on how to get this working is appreciated! I am currently getting RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Invalid Schnorr signature)"}. The ordinal I am trying to use as the parent is here: https://mempool.space/testnet/tx/e13f106578fe0955b375cfb66e4a212fd794b7168a7da5873fada2b78c47e035

image
image
image

Taptree Example sounds strange

It seems that the example is generating a public key (to which the funds need to be sent) in such a way that the pubkey depends on the target script that we want the spender to verify.

If I'm not mistaken the power of Taproot is to enable the commitment of funds to a not-pubic unlocking MAST (the whole Tree) and allow the spender to satisfy just one of the possible alternatives, inside the MAST. that way we are actually not using the MAST feature. The pubkey actually depends on the unlock script

{"code":-26,"message":"non-mandatory-script-verify-flag (Witness program hash mismatch)"}

when i push tx i got this error...

here is my code

async function deploy(
    secret: string,
    brc20: string,
    totalSupply: number,
    mintLimit: number,
    input: {txId: string; index: number; amount: number},
    network: Networks,
) {
    const text = `{"p":"brc-20","op":"deploy","tick":"${brc20}","max":"${totalSupply}","lim":"${mintLimit}"}`
    const inscription = createTextInscription(text)

    const seckey = keys.get_seckey(secret)
    const pubkey = keys.get_pubkey(secret, true)
    const tweakKey = Tap.tweak.getPubKey(pubkey)

    const script = [
        tweakKey,
        "OP_CHECKSIG",
        "OP_0",
        "OP_IF",
        Buffer.from(encoder.encode("ord")),
        "01",
        inscription.contentType,
        "OP_0",
        inscription.content,
        "OP_ENDIF",
    ]
    const tapleaf = Tap.encodeScript(script)
    const [tpubkey, cblock] = Tap.getPubKey(tweakKey, {target: tapleaf})
    const address = Address.p2tr.fromPubKey(tweakKey, network)
    // const address = Address.p2tr.fromPubKey(tpubkey, network)
    console.log(address, "address")
    const txdata = Tx.create({
        vin: [
            {
                // Use the txid of the funding transaction used to send the sats.
                txid: input.txId,
                // Specify the index value of the output that you are going to spend from.
                vout: input.index,
                // Also include the value and script of that ouput.
                prevout: {
                    // Feel free to change this if you sent a different amount.
                    value: input.amount,
                    // 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: 1000,
                // This is the new script that we are locking our funds to.
                scriptPubKey: Address.toScriptPubKey("tb1q7gnys2cwhkm7r73px6nus0g9dcr8mjh6fe2ums"),
            },
            {
                value: input.amount - 5000,
                scriptPubKey: Address.toScriptPubKey(address),
            },
        ],
    })
    const sig = Signer.taproot.sign(seckey, txdata, 0, {extension: tapleaf})
    txdata.vin[0].witness = [sig, script, cblock]
    const isValid = Signer.taproot.verify(txdata, 0, {pubkey, throws: true})
    console.log("Your txhex:", Tx.encode(txdata).hex, isValid)
    // console.dir(txdata, {depth: null})
    await boardCast(Tx.encode(txdata).hex)
}

Did I get something wrong?
Thanks!

Add multiple parent to a child inscription

I was trying to add multiple parent to a child inscription .
So , according the the "Ordinal Theory Handbook" ,

image

I follow the first 2 points .According to the second bullet point I spend both parent inscription as first 2 inputs , and the creation of the child inscription is the 3rd input.

But my doubt is in the 3rd bullet point , Which serialised binary inscription ID should I include in the tag 3 the first parent's or the seconds , if i have to add both how do i do it?

According to @cmdcode/tapscript , how will I include 2 parent serialised binary inscription ids whiie designing the script manually

How to generate secret keys

I'm familiar with generating secret keys randomly and also from a secret phrase using bitcoinjs-lib.

How do I do the same thing with Tapscript? For instance, I have my secret phrase but I'm not sure how to format it to a hex string as shown in the examples.

Tap script inquiry

I want to create a specific transaction using tapscript similar to the ones shown in this example and I can't figure out how to put a 7 before the sig inside the witness. Can you please help ?

`const test = async () => {
// Switch this to true to enable console output.
const VERBOSE = true

    // Create a keypair to use for testing.
    const secret = '0a7d01d1c2e1592a02ea7671bb79ecd31d8d5e660b008f4b10e67787f4f24712'
    const seckey = utils.getSecretKey(secret)
    const pubkey = utils.getPublicKey(seckey, true)

    // Specify an array of scripts to use for testing.
    const scripts = [
        [ 7, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
        [ 8, 'OP_EQUALVERIFY', pubkey, 'OP_CHECKSIG' ],
    ]

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

    if (VERBOSE) console.log('tree:', tree)

    // Pick one of our scripts as a target for spending.
    const index  = 0
    const script = scripts[index]
    const target = Tap.encodeScript(script)

    if (VERBOSE) console.log('target:', target)

    // 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, 'testnet')
    if (VERBOSE) console.log('Your address:', address)

    /* 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: '3a9e52dc01bdb22c4b36a0490d73b06392bccf635e06ba3913339e733e7ab9a2',
            // 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: 10_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: 9_000,
            // This is the new script that we are locking our funds to.
            scriptPubKey: Address.toScriptPubKey('2MzcAVUFfHb1ni46AXCn5z7ADQKrKvz52nn')
        }]
    })

    // 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, 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 })

    if (VERBOSE) {
        console.log('Your txhex:', Tx.encode(txdata).hex)
        console.dir(txdata, { depth: null })
    }
}`

Sign transaction error

My code in Typescript const init_sig = Signer.taproot.sign(seckey, init_redeemtx, 0, { extension: init_leaf }); but it throws error:
throw new Error('crypto.getRandomValues must be defined');

In the examples, where is the scriptPubKey address comming from?

This seemingly random address appears in several places but I will use the inscribe.test.ts file as an example https://github.com/cmdruid/tapscript/blob/master/test/example/taproot/inscribe.test.ts. Notice this code:

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')
      }]
    })

See scriptPubKey: Address.toScriptPubKey('bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y')... Why is bcrt1q6zpf4gefu4ckuud3pjch563nm7x27u4ruahz3y being used as the script that is locking the funds? Is this just an address chosen at random or is it derived from another piece of code that I'm missing?

Does it just mean that address where the UTXO is being spent chosen at random?

Custom Address Prefix

It would be useful to have the ability to use custom address prefixes while doing address encoding. For example Litecoin. Found the encoding for it here.

sponsors?

please, allow the community to send donations to support an incredible project

Public key version reserved for soft-fork upgrades

Hi, I'm playing with taproot scripts via tapscript and bumped into this blocker when trying to send raw transaction on Bitcoin Signet via bitcoin-cli -signet sendrawtransaction:

error code: -26
error message:
non-mandatory-script-verify-flag (Public key version reserved for soft-fork upgrades)

Here is the code I'm using:

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

const secret = '.....'
const seckey = utils.keys.get_seckey(secret)
const pubkey = utils.keys.get_pubkey(seckey)

const script = [....]
const tapleaf = Tap.encodeScript(script)
const [tpubkey, cblock] = Tap.getPubKey(pubkey, {target: tapleaf})
const address = Address.p2tr.fromPubKey(tpubkey, 'signet')

const txdata = Tx.create({
      vin: [{
          txid: '.....',
          vout: 1,
          prevout: {
              value: 100000,
              scriptPubKey: ['OP_1', tpubkey]
          },
      }],
      vout: [
          {
              value: 90000,
              scriptPubKey: Address.toScriptPubKey('tb1p.....')
          }
      ]
  })
const sig = Signer.taproot.sign(seckey, txdata, 0, {extension: tapleaf, throws: true})
txdata.vin[0].witness = [sig.hex, script, cblock]

await Signer.taproot.verify(txdata, 0, {pubkey, throws: true})

Is there anything wrong with how get_pubkey derives public key? Or any other reason I might be getting this strange error? Thanks!

non-mandatory-script-verify-flag (Witness program hash mismatch)

I have this transaction created with tapscript according to the inscription example , decoded from hex:

{
    "addresses": [
        "tb1peetwkzdue9eey9at87d72ea98zw4yz62mq5j49w8utcf28z4w4dq49yt5z",
        "tb1pezwxd60p00laqcu6fvze0canaczel8rdh44u4yu5995xlczgk4asm3hksg"
    ],
    "block_height": -1,
    "block_index": -1,
    "confirmations": 0,
    "double_spend": false,
    "fees": 1000,
    "hash": "1899989d5f95661d60b8b5de873252085043cb528fe75b32040169335790421d",
    "inputs": [
        {
            "addresses": [
                "tb1peetwkzdue9eey9at87d72ea98zw4yz62mq5j49w8utcf28z4w4dq49yt5z"
            ],
            "age": 2536373,
            "output_index": 1,
            "output_value": 20000,
            "prev_hash": "fa2418bba0a89052bb9f045ed3d9b509e58ffb8d76b3b05aa04b1e76dbc1204e",
            "script_type": "pay-to-taproot",
            "sequence": 4294967293,
            "witness": [
                "05cfdbc9641646e51b61dac7ace4436748429004b6e44affbbcbb303126f0ef8a274b4d9d6fcabd3f0eb1b44f9b1e9f6e00b34a218caae4d73d7b50bbcde014e",
                "21021344f6fb0eb5b4ad25dc7db247573e92a1bd2f342317db3e931972434fb9b542ac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38000068",
                "c11344f6fb0eb5b4ad25dc7db247573e92a1bd2f342317db3e931972434fb9b542"
            ]
        }
    ],
    "opt_in_rbf": true,
    "outputs": [
        {
            "addresses": [
                "tb1pezwxd60p00laqcu6fvze0canaczel8rdh44u4yu5995xlczgk4asm3hksg"
            ],
            "script": "5120c89c66e9e17bffd0639a4b0597e3b3ee059f9c6dbd6bca939429686fe048b57b",
            "script_type": "pay-to-taproot",
            "value": 19000
        }
    ],
    "preference": "low",
    "received": "2023-11-02T15:54:02.249240475Z",
    "relayed_by": "44.202.163.146",
    "size": 268,
    "total": 19000,
    "ver": 2,
    "vin_sz": 1,
    "vout_sz": 1,
    "vsize": 138
}

When I try to broadcast the transaction I get this error: 'sendrawtransaction RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Witness program hash mismatch)"}'

Witness program hash mismatch

test/example/taproot/inscribe.test.ts
`
import { Buff } from '@cmdcode/buff'
import { taproot } from '../../../src/lib/sig/index.js'
import { tap_pubkey } from '../../../src/lib/tap/index.js'
import { assert } from '../../../src/index.js'

import {
get_seckey,
get_pubkey
} from '@cmdcode/crypto-tools/keys'

import {
parse_addr,
P2TR
} from '../../../src/lib/addr/index.js'

import {
encode_tx,
parse_tx
} from '../../../src/lib/tx/index.js'

import { Buffer } from 'buffer/'

const VERBOSE = false

function inscription() {

const ret_addr = "tb1ptv0luvng53jlddlae7y2898pgs9cqfxsaw0qsrcz4k0kvjsdtnsqvkvyzg"
const encoder = new TextEncoder()
try {
const text = {"p":"brc-20","op":"deploy","tick":"hffp","max":"210000","lim":"1"}
const imgdata = Buffer.from(encoder.encode(text))
const marker = Buff.encode('ord')
const mimetype = Buffer.from(encoder.encode('text/plain;charset=utf-8'))
const secret = 'your secret'
const seckey = get_seckey(secret)
const pubkey = get_pubkey(seckey, true)
const script = [pubkey, 'OP_CHECKSIG', 'OP_0', 'OP_IF', marker, '01', mimetype, 'OP_0', imgdata, 'OP_ENDIF']
const { tapkey, cblock } = tap_pubkey(pubkey, { script })
const address = P2TR.create(tapkey, 'testnet')
const txinput = {
"txid": "201d025a29b20985a329947910c293ce9640c639c834c416dcf6440d3ad4697d",
"vout": 0,
"satoshi": 4000000,
"scriptType": "5120",
"scriptPk": "51206602f211fb1a7a56c3c45422fdc5509876af35c095c4973029ae57ea7974edbc",
"codeType": 9,
"address": "tb1pvcp0yy0mrfa9ds7y2s30m32snpm27dwqjhzfwvpf4et757t5ak7qq66r4h",
"height": 2543743,
"idx": 67,
"isOpInRBF": false,
"inscriptions": []
}

const txdata = parse_tx({
  vin: [{
    // Use the txid of the funding transaction used to send the sats.
    txid: txinput.txid,
    // Specify the index value of the output that you are going to spend from.
    vout: txinput.vout,
    // Also include the value and script of that ouput.
    prevout: {
      // Feel free to change this if you sent a different amount.
      value: txinput.satoshi,
      // This is what our address looks like in script form.
      scriptPubKey: ["OP_1", tapkey],
    },
  }],
  vout: [{
    value: 5000,
    scriptPubKey: parse_addr(ret_addr).asm
  }]
})

const sig = taproot.sign_tx(seckey, txdata, { script, txindex: 0 })

txdata.vin[0].witness = [sig, script, cblock]

const is_valid = taproot.verify_tx(txdata, { txindex: 0, pubkey, throws: true })
assert.ok(is_valid, 'Transaction failed validation.')
const txhex = encode_tx(txdata)
console.log(txhex.toString())

} catch (err) {
}
}

inscription()
`

020000000001017d69d43a0d44f6dc16c434c839c64096ce93c210799429a38509b2295a021d200000000000fdffffff0188130000000000002251205b1ffe3268a465f6b7fdcf88a394e1440b8024d0eb9e080f02ad9f664a0d5ce00340477a3553bc207d380298787fa597e40cd67fa92b5304f1e3826477c9e7792259e75cf72132c0c834d72717aa60ea019146b241ba77a4d08cfb24a24fc8f55cf18820fcf61b477e3ba7ed2fe14521da13960b61332f660204f6089bae65c6ba145685ac0063036f72645118746578742f706c61696e3b636861727365743d7574662d3800437b2270223a226272632d3230222c226f70223a226465706c6f79222c227469636b223a2268666670222c226d6178223a22323130303030222c226c696d223a2231227d6821c0fcf61b477e3ba7ed2fe14521da13960b61332f660204f6089bae65c6ba14568500000000

sendrawtransaction RPC error: {"code":-26,"message":"non-mandatory-script-verify-flag (Witness program hash mismatch)"}

crypto.getRandomValues must be defined node js error

Hello I get the following error in node js:

/@cmdcode/tapscript/dist/module.js:104
    throw new Error('crypto.getRandomValues must be defined');

As a local work around I changed (line 56 in module.js)

const crypto$2 = typeof globalThis === 'object' && 'crypto' in globalThis ? globalThis.crypto : undefined;

to

import * as crypto$2  from 'crypto';

How can I decode script from tpubkey

const script  = [ pubkey, 'OP_CHECKSIG', 'OP_0', 'OP_IF', marker, '01', mimetype, 'OP_0', imgdata, 'OP_ENDIF' ]

const tapleaf = Tap.encodeScript(script)

const [ tpubkey, cblock ] = Tap.getPubKey(pubkey, { target: tapleaf })

How can I decode script from tpubkey

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.