Taproot part1: Generating P2TR KeyPath Witness Programs 🚀

Published: (January 19, 2026 at 02:48 PM EST)
4 min read
Source: Dev.to

Source: Dev.to

Welcome back, dear readers! If you’re here, it means you’ve survived the setup and you’re ready to get your hands dirty with some actual Bitcoin internal magic.

In my previous log, we got our Signet nodes up, running, and stealthily “cloaked” for the BOSS (Bitcoin Open Source Software) Challenge. Now, it’s time for the first real trial: Challenge 1 – Recover Wallet State.

The quest? Compute your wallet balance by scanning the blockchain manually. The catch? The evaluation environment has the Bitcoin Core wallet disabled. No getbalance, no shortcuts. Just you, your code, and the raw blocks. Let’s go! ⚡️

🛠 Step 0: The “Sanity Check” (CLI)

Before we start automating, we need a “North Star” to make sure we aren’t chasing ghosts. We’ll use the Bitcoin CLI to see what our balance should be. This is your benchmark!

1. Create your local wallet

docker exec bitcoin_signet bitcoin-cli -signet -datadir=/home/bitcoin/.bitcoin \
  createwallet "boss" false false "" false true true

2. Import that descriptor

docker exec bitcoin_signet bitcoin-cli -signet -datadir=/home/bitcoin/.bitcoin \
  -rpcwallet=boss importdescriptors '[{ "desc": "tr(tprv8ZgxMBicQKsPfCkDmSmHXuKAVA8zwwXLWTHEgyyisMioZ8gn3nTmmNwUzBMKWnNMkhRYN3E9qVCZrZ6MC645cqYdpy9jTYHhBwG1KhpNAMd/86h/1h/0h/0/*)#hjz07900", "timestamp": 0, "active": true }]'

3. Peek at the results

docker exec bitcoin_signet bitcoin-cli -signet -datadir=/home/bitcoin/.bitcoin \
  -rpcwallet=boss getwalletinfo

If you see a balance, BOOM! 💥 You now know the number you’re aiming for. Let’s write the code to find it manually.

🧬 The Deep Dive: P2TR Derivation

To find our coins, we need the Witness Program. In the land of Taproot (BIP 341), the address is derived from a “tweaked” public key. Even if we’re just using the Key Path (no complex scripts), we have to commit to a script path.

The “Golden” Benchmarking Script

We’re using bitcoinjs-lib and tiny-secp256k1 to handle the heavy elliptic‑curve math.

CRITICAL NOTE: You cannot use this JavaScript program for your final submission! The BOSS challenge strictly requires Python, C, C++, or Rust. Use this JS script only as a benchmark to verify your logic. If your Python/Rust code generates the same strings as this script, you’re on the right track!

import { BIP32Factory } from "bip32";
import ECPairFactory from "ecpair";
import * as ecc from "tiny-secp256k1";
import * as bitcoin from "bitcoinjs-lib";
import fs from "fs";

// Initialize the ECC library—Taproot needs this!
bitcoin.initEccLib(ecc);

export const bip32 = BIP32Factory(ecc);
export const ECPair = ECPairFactory(ecc);

const net = bitcoin.networks.testnet;
const xpriv = bip32.fromBase58(
  "tprv8ZgxMBicQKsPfF4wki3EzsSYvohmC5z2B92m74LscyiFAHwZUtn4Lhbfosc1zCQRzN4aXiPqiH64xbTbsnRCgTskJWspELwAYSdyZSekqyp",
  net,
);

// Derive the path from the descriptor: m/86'/1'/0'/0
const derived = xpriv.derivePath("m/86'/1'/0'/0");
const addresses = [];
const internals = [];
const witnesses = [];

console.log("🚀 Starting derivation for 2000 keys...");

for (let i = 0; i < 2000; i++) {
  const current = derived.derive(i);

  // Extract the X‑only internal pubkey (32 bytes)
  const internalPubkey = current.publicKey.slice(1, 33);

  // Generate the P2TR payment—bitcoinjs handles the TapTweak automatically!
  const { address, pubkey } = bitcoin.payments.p2tr({
    internalPubkey,
    network: net,
  });

  addresses.push(address);
  witnesses.push(pubkey?.toHex()); // This is your Witness Program!
  internals.push(internalPubkey.toHex());
}

// Write to files so you can 'diff' against your main submission code
fs.writeFileSync("addresses.json", JSON.stringify(addresses, null, 2));
fs.writeFileSync("witnesses.json", JSON.stringify(witnesses, null, 2));
fs.writeFileSync("internals.json", JSON.stringify(internals, null, 2));

console.log("✅ Done! Check your JSON files and compare them with your challenge code.");

🎯 Why This Is Your Secret Weapon

The BOSS Challenge requires you to:

  • Derive 2000 keys.
  • Compute the P2TR witness program for each.
  • Scan the first 300 blocks of the Signet chain.

The Witness Program (the pubkey in the script above) is the actual hex string you’ll be looking for inside the scriptPubKey of transaction outputs on‑chain. Using this JS benchmark helps you isolate derivation bugs from scanning bugs.

⏭ What’s Next?

Now that you have your list of 2000 witness programs in witnesses.json, it’s time to go hunting! Grab the blocks, iterate through the transactions, and start tallying that balance.

Are you smashing the challenge in Rust or Python? Drop a comment below and let’s talk TapTweak math! 🚀🔥

Back to Blog

Related posts

Read more »

𝗗𝗲𝘀𝗶𝗴𝗻𝗲𝗱 𝗮 𝗣𝗿𝗼𝗱𝘂𝗰𝘁𝗶𝗼𝗻‑𝗥𝗲𝗮𝗱𝘆 𝗠𝘂𝗹𝘁𝗶‑𝗥𝗲𝗴𝗶𝗼𝗻 𝗔𝗪𝗦 𝗔𝗿𝗰𝗵𝗶𝘁𝗲𝗰𝘁𝘂𝗿𝗲 𝗘𝗞𝗦 | 𝗖𝗜/𝗖𝗗 | 𝗖𝗮𝗻𝗮𝗿𝘆 𝗗𝗲𝗽𝗹𝗼𝘆𝗺𝗲𝗻𝘁𝘀 | 𝗗𝗥 𝗙𝗮𝗶𝗹𝗼𝘃𝗲𝗿

!Architecture Diagramhttps://dev-to-uploads.s3.amazonaws.com/uploads/articles/p20jqk5gukphtqbsnftb.gif I designed a production‑grade multi‑region AWS architectu...