Blockchains

Bitcoin Gold

Bitcoin's driver with Bitcoin Gold's prefixes and preamble. Four address formats on mainnet and testnet and no taproot at all.
driver
bitcoingold
curve
secp256k1
address formats
legacy, p2sh, segwit, p2wsh
SLIP-44 coin
156'

Load it

import { useBlockchain, blockchains } from "@agntn/keys";

const btgChain = useBlockchain(await blockchains.bitcoingold()());
const testnet = useBlockchain(await blockchains.bitcoingold({ network: "testnet" })());

Mainnet and testnet only, anything else throws in the constructor. Testnet, signet and regtest share one set of version bytes in the node, and they're Bitcoin's testnet bytes.

Four formats, not five

Bitcoin Gold forked off Bitcoin in 2017, after SegWit was already live, so it kept SegWit. The prefixes are its own.

TypeMainnetTestnetEncoding
legacy (default)G...m... or n...base58check, version 0x26 / 0x6f
p2shA...2...base58check, version 0x17 / 0xc4
segwitbtg1q...tbtg1q...bech32, witness v0, P2WPKH
p2wshbtg1q...tbtg1q...bech32, witness v0, script hash
const privateKey = "0000000000000000000000000000000000000000000000000000000000000001";
const publicKey = btgChain.getKeyPublic(privateKey);

btgChain.getAddress(publicKey); // GUXByHDZLvU4DnVH9imSFckt3HEQ5cFgE5
btgChain.getAddress(publicKey, "p2sh"); // AZ1BpW94ubqHRzsqdfoFCMgVyN1H4CnSEp
btgChain.getAddress(publicKey, "segwit"); // btg1qw508d6qejxtdg4y5r3zarvary0c5xw7k6w057a

Look at the segwit one next to Bitcoin's bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4. Same hash in the middle, different prefix and checksum. The testnet legacy address is byte for byte Bitcoin's testnet address, so an m address alone doesn't say which chain it's on.

Why there's no taproot

The node has the Taproot deployment copied from Bitcoin Core, signaling window included: April to August 2021. But no Bitcoin Gold release came out in that window. v0.17.3 came out in 2020, v0.21.3 in December 2024, long after the window closed. Nobody could signal, so the deployment failed and never activated.

So the driver refuses it everywhere. getAddress(publicKey, "taproot") throws a RangeError, and deriveHDWallet on an m/86' path throws the same way. validateAddress returns false for btg1p... and every later witness version, even though the node decodes them fine. Without Taproot rules a witness v1 output is spendable by anyone. Calling that address valid would just set a trap.

Validation

btgChain.validateAddress("GUHcigT74ggLsmbxHFTLfn2ZUNJUWiXaMG"); // true
btgChain.validateAddress("btg1q5cuatynjmk4szh40mmunszfzh7zrc5xmn8padv"); // true
btgChain.validateAddress("btg1p5rgvqejqh9dh37t9g94dd9cm8vtqns7dndgj423egwggsggcdzms7pg7wc"); // false, witness v1
btgChain.validateAddress("1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"); // false, Bitcoin's version byte

The first three come straight from the node's key_io_valid.json. A testnet address fails on mainnet and the other way round.

Mnemonics

BIP32 on coin type 156 from SLIP-0044, the one Trezor uses too. The purpose level picks the format, the same as on Bitcoin.

const mnemonic =
  "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

btgChain.deriveHDWallet(mnemonic, "m/44'/156'/0'/0/0").address; // GeTZ7bjfXtGsyEcerSSFJNUSZwLfjtCJX9
btgChain.deriveHDWallet(mnemonic, "m/49'/156'/0'/0/0").address; // AL8uaqKrP4n61pb2BrQXpMC3VcUdjmpAwn
btgChain.deriveHDWallet(mnemonic, "m/84'/156'/0'/0/0").address; // btg1qkwnu2phwvard2spr2n0a9d84x590ahywl3yacu

WIF is Bitcoin's, prefix 0x80 on mainnet and 0xef on testnet. encodeWIF(privateKey, { chain: "bitcoin" }) gives a string a Bitcoin Gold wallet imports.

Signing

The node signs with "\x1dBitcoin Gold Signed Message:\n", so a Bitcoin signature doesn't verify here and the other way round. The rest is Core's recipe: compact size lengths, double SHA-256, secp256k1. What comes back is 64 bytes of r||s hex. The node's own rpc_signmessage.py vector matches byte for byte once you drop the header byte from its base64. { recovered: true } throws, like on Bitcoin.

The replay protection Bitcoin Gold added at the fork, SIGHASH_FORKID, is about transactions. Signed messages never had it, and this library doesn't sign transactions anyway.

Where it lives

src/blockchains/bitcoingold.ts has the network table, the preamble and the taproot refusal. Everything else comes from AbstractBitcoinBlockchain in src/utils/bitcoin.ts, shared with Bitcoin and Litecoin.

@agntn/keys·MIT license· Keys never leave the browser.