Skip to content

beep-boopp/chainlens

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ChainLens

A Bitcoin protocol analyzer written in Go. Parses raw Bitcoin transactions and block-file data directly from the wire format — no external APIs, no Bitcoin node required. Ships as both a JSON-emitting CLI and a self-hosted web visualizer.


Architecture

The codebase is a single Go package with a dual-entry-point design:

Mode Entry Build
CLI main.go go run .
Web server web.go (//go:build web) go run -tags web web.go ...

web.go carries a //go:build web constraint so the two entry points never conflict during a plain go build. Both modes share the same parser, script, and hash layers.

main.go / web.go   ← entry points (CLI flags | HTTP handlers)
      │
      ├─ parser.go  ← raw-byte transaction deserialization
      ├─ block.go   ← blk*.dat / rev*.dat parsing + XOR decoding
      ├─ undo.go    ← UTXO undo-record deserialization + decompression
      ├─ script.go  ← script classification, disassembly, OP_RETURN decoding
      ├─ address.go ← address derivation (Base58Check, Bech32, Bech32m)
      ├─ hash.go    ← double-SHA256, TXID/WTXID, Merkle root
      └─ report.go  ← JSON report assembly

Technical highlights

Binary transaction deserialization (parser.go)

Parses the Bitcoin wire format byte-by-byte: 4-byte little-endian version, VarInt input/output counts, per-input scriptSig, 4-byte sequence, per-output value and scriptPubKey, and SegWit witness stacks. SegWit transactions are detected by the 0x00 0x01 marker/flag pair immediately after the version field (BIP141). Legacy and SegWit code-paths diverge at that branch point and reconverge for locktime.

XOR-obfuscated block-file parsing (block.go)

Bitcoin Core obfuscates blk*.dat and rev*.dat on disk by XOR-ing every byte against a rotating key stored in xor.dat. block.go strips this layer before any parsing begins, then seeks the 0xD9B4BEF9 magic bytes that separate blocks within the file, and extracts the 80-byte block header followed by the transaction vector.

UTXO undo-data deserialization with amount decompression (undo.go)

The undo (revert) files store prevout values in Bitcoin Core's custom base-128 VarInt encoding with a non-linear compression scheme. undo.go implements ReadBase128VarInt and the full DecompressAmount inverse function to recover satoshi values. Compressed scripts are reconstructed via DecompressScript, which handles all five nSize codes (P2PKH, P2SH, compressed P2PK with even/odd Y, uncompressed P2PK) — including secp256k1 Y-coordinate recovery from X using the modular square root y = (x³+7)^((P+1)/4) mod P.

Merkle root computation and verification (hash.go)

After all transactions in a block are parsed, the engine recomputes the Merkle root from scratch: each TXID is double-SHA256 hashed, byte-reversed to standard display order, then the tree is built bottom-up with the Bitcoin-standard odd-node duplication rule. The result is compared against the 32-byte field in the 80-byte block header to cryptographically verify the transaction set.

Script classification and disassembly (script.go)

Classifies every scriptPubKey and scriptSig into one of: p2pkh, p2sh, p2wpkh, p2wsh, p2tr, op_return, or unknown. Input spend-types include nested SegWit (p2sh-p2wpkh, p2sh-p2wsh) and Taproot keypath vs scriptpath (distinguished by witness stack size and control-block prefix byte). Full opcode disassembly covers all Bitcoin Core opcodes; OP_RETURN payloads are decoded with support for OP_PUSHDATA1/2/4 and multiple concatenated pushes.

SegWit weight and virtual-byte accounting (BIP141)

Weight is computed as (non_witness_bytes × 4) + witness_bytes, then converted to vbytes via ceiling division. The report also computes a segwit_savings breakdown showing weight_if_legacy vs weight_actual and the resulting savings_pct — the exact metric miners use for block-space pricing.

Address derivation (address.go)

  • P2PKH / P2SH — Base58Check encoding with the correct mainnet version byte.
  • P2WPKH / P2WSH — Bech32 encoding (bc1q...) per BIP173.
  • P2TR — Bech32m encoding (bc1p...) per BIP350.

BIP68 relative timelocks and BIP125 RBF detection

Each input's sequence field is decoded: bit 31 disables relative timelocks, bit 22 selects time vs block units, and the low 16 bits carry the value (×512 seconds for time-based). BIP125 RBF is flagged when any input has sequence < 0xFFFFFFFE.


Prerequisites

  • Go 1.23+
go version   # should print go1.23 or higher

Quick start

CLI — single transaction

go run . fixtures/transactions/tx_legacy_p2pkh.json
# JSON report printed to stdout and written to out/<txid>.json

Or via the shell wrapper:

./cli.sh fixtures/transactions/tx_legacy_p2pkh.json

CLI — block files

# Decompress sample fixtures first (one-time)
gunzip -k fixtures/blocks/*.dat.gz

./cli.sh --block fixtures/blocks/blk04330.dat \
                 fixtures/blocks/rev04330.dat \
                 fixtures/blocks/xor.dat
# Writes out/<block_hash>.json for every block in the file

Web visualizer

./web.sh          # starts on http://127.0.0.1:3000
PORT=8080 ./web.sh

API endpoints:

  • GET /api/health{ "ok": true }
  • POST /api/analyze → transaction analysis (JSON body: fixture format)
  • POST /api/analyze-block → block analysis (multipart: blk, rev, xor)

Fixture format (transaction mode)

{
  "network": "mainnet",
  "raw_tx": "0200000001...",
  "prevouts": [
    {
      "txid": "aa...11",
      "vout": 0,
      "value_sats": 123456,
      "script_pubkey_hex": "0014..."
    }
  ]
}

prevouts do not need to be in the same order as the transaction inputs — they are matched by (txid, vout) tuple.


JSON output schema (transaction)

{
  "ok": true,
  "network": "mainnet",
  "segwit": true,
  "txid": "...",
  "wtxid": "...",
  "version": 2,
  "locktime": 800000,
  "size_bytes": 222,
  "weight": 561,
  "vbytes": 141,
  "total_input_sats": 123456,
  "total_output_sats": 120000,
  "fee_sats": 3456,
  "fee_rate_sat_vb": 24.51,
  "rbf_signaling": true,
  "locktime_type": "block_height",
  "locktime_value": 800000,
  "segwit_savings": { "..." : "..." },
  "vin": [ "..." ],
  "vout": [ "..." ],
  "warnings": [ { "code": "RBF_SIGNALING" } ]
}

On error:

{ "ok": false, "error": { "code": "INVALID_TX", "message": "..." } }

Supported script types

Type Input classify Output classify Address
P2PKH 1...
P2SH 3...
P2WPKH bc1q...
P2WSH bc1q...
P2TR keypath bc1p...
P2TR scriptpath bc1p...
P2SH-P2WPKH
P2SH-P2WSH
OP_RETURN null

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors