A private key can participate in several Bitcoin output constructions. The earlier example derives legacy P2PKH, nested P2SH-P2WPKH, native P2WPKH, and a Taproot key-path address from one key. These are different scripts and encodings, not four unrelated private keys.
| Form | What the example constructs |
|---|---|
| P2PKH | A legacy public-key-hash payment |
| P2SH-P2WPKH | A SegWit redeem script wrapped by P2SH |
| P2WPKH | A native SegWit public-key-hash output |
| P2TR | A tweaked x-only public-key output |
Historical TypeScript source
Read the address derivation notebook
// "bitcoinjs-lib": "^6.1.5",
// "dotenv": "^16.0.3",
// "ecpair": "^2.1.0",
// "tiny-secp256k1": "^2.2.3"
// https://bitcoin.design/guide/glossary/address/
// https://en.bitcoin.it/wiki/Invoice_address
require('dotenv').config();
import { ECPairFactory, ECPairInterface } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import * as bitcoin from 'bitcoinjs-lib';
import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371';
const ECPair = ECPairFactory(ecc);
bitcoin.initEccLib(ecc);
const isDev = process.env.NODE_ENV === 'development';
const network = isDev ? bitcoin.networks.testnet : bitcoin.networks.bitcoin;
/**
* Prepare wallet from a Wallet Import Format (WIF) private key
* Each network -> Each address has own private key.
*/
export const initKeyPair = (WIF = process.env.WALLET_PRIVATE_KEY) => {
if (!WIF) throw new Error('WALLET_PRIVATE_KEY is not set');
// Decode WIF private key
const keyPair = ECPair.fromWIF(WIF, network);
return {
keyPair,
publicKey: keyPair.publicKey,
};
};
// Native Segwit P2WPKH, bech32 address type with 42 chars. Start with tb1q or bc1q
// Ex: tb1q2fzh9f7ss8eu56fnv2zhr56a0wd3g64xp7d4xk
export const getNativeSegwitP2WPKH = (publicKeyBuffer: Buffer) => {
return bitcoin.payments.p2wpkh({
pubkey: publicKeyBuffer,
network: network,
});
};
// Nested Segwit P2SH(P2WPKH) with 35 chars
// Ex: 2N2EUQ12PhPr9Q7UKdp4xSReKgcVGUSUaYp
export const getNestedSegwitP2SH_P2WPKH = (publicKeyBuffer: Buffer) => {
return bitcoin.payments.p2sh({
redeem: bitcoin.payments.p2wpkh({ pubkey: publicKeyBuffer, network: network }),
network: network,
});
};
// P2PKH type with 34 chars
// Ex: mo1xu79z1354YicBa5EXwMh7HrCAfRdPSW
export const getLegacyP2PKH = (publicKeyBuffer: Buffer) => {
return bitcoin.payments.p2pkh({ pubkey: publicKeyBuffer, network: network });
};
// Taproot P2TR with 62 chars. Start with tb1p or bc1p
// Ex: tb1pmamsssqlv0j7dda8g04phcsn0qzndy6kff4hq293e85drju0f7sq5uzedx
export const getTaprootP2TR = (keyPair: bitcoin.Signer) => {
function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion,@typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let privateKey: Uint8Array | undefined = signer.privateKey!;
if (!privateKey) throw new Error('Private key is required for tweaking signer!');
if (signer.publicKey[0] === 3) privateKey = ecc.privateNegate(privateKey);
function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
return bitcoin.crypto.taggedHash('TapTweak', Buffer.concat(h ? [pubKey, h] : [pubKey]));
}
const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash));
if (!tweakedPrivateKey) throw new Error('Invalid tweaked private key!');
return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
network: opts.network,
});
}
// Tweak the original keypair
const tweakedSigner = tweakSigner(keyPair, { network });
// Generate an address from the tweaked public key
return bitcoin.payments.p2tr({
pubkey: toXOnly(tweakedSigner.publicKey),
network,
});
};
export const initWallet = () => {
const { keyPair, publicKey } = initKeyPair();
console.log('Native Segwit Address:', getNativeSegwitP2WPKH(publicKey).address);
console.log('Nested Segwit Address:', getNestedSegwitP2SH_P2WPKH(publicKey).address);
console.log('Legacy Address:', getLegacyP2PKH(publicKey).address);
console.log('Taproot Address:', getTaprootP2TR(keyPair).address);
// Native Segwit Address: tb1q0u54lxn4gtfhntkzqlg3c0n3xgrcwynezuhuw4
// Nested Segwit Address: 2N2EUQ12PhPr9Q7UKdp4xSReKgcVGUSUaYp
// Legacy Address: ms7Kg6REg2cA3f953gv1G6n79QB4Bg2hgu
// Taproot Address: tb1p43ucsc2h6pfw08xsszgjjjz9tjn9ck4ezg5fxuvlu8mghwhgxyfsne5n3g
};
The source records bitcoinjs-lib 6.1.5, ecpair 2.1.0, and tiny-secp256k1 2.2.3 ranges. It imports an internal bitcoinjs-lib helper, so its module path is version-sensitive. No private key appears in the listing; WIF is read from an environment variable.
The Taproot branch handles parity and the TapTweak before constructing the output. Address prefix/length comments describe these examples and are not general validators for every script or network.
The source selects testnet only when NODE_ENV equals development; every other value selects mainnet. That default matters before any reuse. No key was loaded, derived address generated, or network request made for this revision. Source: Gist revision.