Skip to content

thickzero/TxEcho

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TxEcho

TxEcho

A tiny Bitcoin P2P payment monitor. One Go file, no deps.

TxEcho joins the Bitcoin gossip network, watches tx and block relay, and publishes fresh incoming native-SegWit outputs as flat JSON. Local dir, its own HTTP handler, or a CDN. Wallets poll to spot payments without running a node, and without leaking what they watch: outputs bucket by sha256(scriptPubKey), so the server sees a bucket number shared by thousands of scripts, not an address.

Not a node. See Trust.

Quickstart

Grab a release binary and verify it:

sha256sum -c SHA256SUMS

Or install from source:

go install github.com/thickzero/TxEcho@latest

Then:

./txecho doctor            # sanity check
./txecho once -duration 2m # collect briefly, publish, exit
./txecho run               # keep tapping peers, publish every minute

Artifacts

A client only needs two paths:

manifest.json
b/<bucket>.json

Manifest:

{
  "version": 1,
  "row_version": 1,
  "bucket_bits": 8,
  "tip_height": 956806,
  "tip_hash": "...",
  "buckets": { "2a": {"path": "b/2a.json", "sha256": "...", "count": 1} }
}

Rows are compact arrays:

["scriptHash", "txid", 0, 12345, "m"]                        // mempool
["scriptHash", "txid", 0, 12345, "c", 956806, "blockHash"]   // confirmed

Absent bucket = empty. Buckets that had rows and later empty out get an explicit [] so old rows do not linger in cache.

Client model

  1. spk = hex of the output's scriptPubKey (lowercase, no 0x).
  2. Hash the ASCII bytes of that hex with SHA-256.
  3. Top bucket_bits of the digest is the bucket. Format as lowercase hex, (bucket_bits+3)/4 chars.
  4. Fetch manifest.json, then the bucket if it is listed.
  5. Verify the bucket body SHA-256 against the manifest. On mismatch, cache-bust or wait one TTL.
  6. Exact-match the 128-bit script hash before trusting a row.

Confirmations = tip_height − row height. TestClientModelGoldenVector pins a concrete example.

HTTP serving

Same artifacts, straight from memory:

TXECHO_PUBLISHERS=http TXECHO_HTTP_ADDR=:8080 \
  TXECHO_API_KEY=$(openssl rand -hex 16) ./txecho run

Endpoints (GET, HEAD):

/               same as /manifest.json
/manifest.json
/b/<bucket>.json
/healthz        always open

Standard Cache-Control and a strong ETag, so If-None-Match returns 304. With TXECHO_API_KEY set, every endpoint except /healthz needs either X-API-Key: <key> or Authorization: Bearer <key>, compared in constant time. Bind privately or front it with a proxy or Cloudflare Tunnel.

Config

TXECHO_* env, .env, or flags. Flags win.

TXECHO_PEERS            host[:port] list; DNS seeds if unset
TXECHO_BUCKET_BITS      4..16, default 8
TXECHO_RETENTION        rolling window, default 20m
TXECHO_POLL_INTERVAL    publish cadence, default 60s
TXECHO_CHECKPOINT       header floor, height:hash:bits
TXECHO_STATE            state file, default ./txecho-state.json
TXECHO_PUBLISHERS       local,bunny,r2,http (default local)
TXECHO_OUT              local dir, default ./public
TXECHO_HTTP_ADDR        http listen, e.g. :8080
TXECHO_API_KEY          optional key for the http publisher

Full reference lives in the header of txecho.go. Remote publishers take their credentials from BUNNY_* and R2_*. Unchanged objects are not re-uploaded.

Trust

TxEcho discovers incoming outputs. It does not track spends, balances, histories, or wallet activity.

Pending rows ("m") are unvalidated mempool hints from peers, only for txs TxEcho itself requested after an inv. Cold-start mempool requests are often ignored, so relay and recent-block backfill do most of the work.

Confirmed rows ("c") mean the block was parsed by TxEcho, its header extends the verified chain within the last 100 blocks, its target clears the checkpoint difficulty floor (widened 4x so a normal downward retarget cannot freeze the tip), its merkle root matches the header, and it has no duplicate txids (CVE-2012-2459 rejected). Stronger than trusting a stray payload, weaker than full consensus: no retarget rules, no chainwork weighting, no script validation. Settle real money against a full node.

Short reorgs: a strictly longer branch inside the 100-block window replaces the tip and orphaned confirmations are dropped. Equal-length forks keep first-seen; deeper forks are refused.

Checkpoint and state

Baked default so you do not sync headers from genesis:

956805:00000000000000000001dd42b106f423cecda728647c973dbd5dbb18e6d5b21b:386013762

TXECHO_STATE caches recent headers and published bucket IDs. Wrong network, wrong checkpoint, or a header gap rejects the file and rebuilds from the checkpoint.

Reproducible builds

Static binary, pinned toolchain, byte-for-byte:

docker run --rm -v "$PWD":/src -w /src -e HOME=/tmp \
  --user "$(id -u):$(id -g)" golang:1.26.4-alpine \
  sh -c 'CGO_ENABLED=0 go build -trimpath -buildvcs=false -ldflags="-s -w" -o txecho .'
sha256sum txecho

Rebuild it. No trust required.

License

MIT. On the wire it says /txecho:0.1.0/

About

A tiny Bitcoin P2P payment monitor. One Go file, no deps.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages