fluxrpc/base58: fast Base58 for Solana-shaped Go
Technical guide to github.com/fluxrpc/base58 — optimized Go Base58 encode/decode for 32-byte pubkeys and 64-byte signatures (Firedancer-style matmul/AVX2), Append* zero-alloc paths, batch encode, EncodeCached32. API, perf vs solana-go, when to use it.
devrels.xyz/a/232short linkfluxrpc/base58 is a Go library for Base58 encode/decode tuned for Solana-shaped payloads: 32-byte public keys and 64-byte signatures. Built and used at FluxRPC for high-volume RPC rendering. Generic lengths use a limb-based codec; 32/64 use Firedancer-style matrix-multiply fast paths (AVX2 on amd64, with scalar assembly and pure-Go fallbacks).
Part of the FluxRPC stack (org fluxrpc, founder @CloakdDev).
People and links
| Role | Handle / URL |
|---|---|
| Repo | github.com/fluxrpc/base58 |
| Org | FluxRPC · github.com/fluxrpc · @fluxrpc |
| Founder | Scott · CloakdDev · @CloakdDev · github.com/cloakd |
Install
go get github.com/fluxrpc/base58@latestAPI surface
import "github.com/fluxrpc/base58"
// Variable length (dispatches to 32/64 fast paths when applicable)
s := base58.Encode(buf)
raw, err := base58.Decode(s)
// Fixed-size (pubkeys / signatures)
s = base58.Encode32(&pk) // *[32]byte
s = base58.Encode64(&sig) // *[64]byte
err = base58.Decode32(s, &pk)
err = base58.Decode64(s, &sig)
// Zero-alloc append (reuse buffer in hot loops)
buf = base58.AppendEncode32(buf[:0], &pk)
buf = base58.AppendEncode64(buf[:0], &sig)
buf, err = base58.AppendDecode(buf[:0], s)
buf = base58.AppendEncode(buf[:0], raw)
// Batch: four keys at a time; strings share one backing buffer
strs := base58.Encode32Batch(pks)
strs = base58.Encode64Batch(sigs)
// Interning cache for hot repeated keys (program IDs, sysvars, mints)
s = base58.EncodeCached32(&programId) // ~6 ns hit, 0 alloc — read collision caveat in docsUpstream notes: Decode32/Decode64 and Append* are zero heap allocations. Plain Encode/Encode32/Encode64 allocate once for the returned string. For RPC JSON assembly, prefer AppendEncode* with a reused buffer.
buf := make([]byte, 0, base58.EncodedMaxLen32) // see package for max lens
for i := range pubkeys {
buf = base58.AppendEncode32(buf[:0], &pubkeys[i]) // 0 allocs/op
writeJSONString(w, buf)
}Why it is fast (architecture)
- 32/64 fixed paths — matrix-multiply style encode (Firedancer / fd_base58 lineage), not classic byte-at-a-time long division
- AVX2 on amd64 when CPUID allows; scalar asm (amd64/arm64) + pure Go otherwise
- Variable lengths — limb-based codec claimed ~10–30× vs naive long division on longer inputs
- Batch — interleaved carry chains, shared string backing (~35% less CPU/key vs looping Encode32 in upstream notes)
- EncodeCached32 — optional intern for repeated keys; document collision tradeoff before enabling in security-sensitive contexts
Correctness: cross-checked against Bitcoin Core / bs58 / fd_base58 / five8 vectors, randomized round-trips, and go test -fuzz. Fallbacks exercised via dedicated tests and multi-arch builds.
Performance (upstream published)
Same machine, Go 1.26, i7-9700K AVX2, single core — generic Encode/Decode vs solana-foundation/solana-go base58 (averages of 5 runs; comparison bench not vendored to stay dependency-free):
| Op | solana-go | fluxrpc/base58 | Speedup |
|---|---|---|---|
| Encode 32B | 364 ns · 2 alloc | 68 ns · 1 alloc | ~5.3× |
| Decode 32B | 142 ns · 2 alloc | 60 ns · 1 alloc | ~2.4× |
| Encode 64B | 1,116 ns · 2 alloc | 153 ns · 1 alloc | ~7.3× |
| Decode 64B | 296 ns · 3 alloc | 128 ns · 1 alloc | ~2.3× |
| Encode 100B | 2,378 ns · 5 alloc | 541 ns · 1 alloc | ~4.4× |
Typed paths (indicative): Encode32 ~63 ns/1 alloc; AppendEncode32 ~41 ns/0; Decode32 ~34 ns/0; EncodeCached32 hit ~5.7 ns/0. Re-bench on your hardware before citing in SLOs.
When to use it
| Use | Skip / careful |
|---|---|
| Go RPC, indexer, or bot encoding many pubkeys/sigs | Non-Go stacks (use native crates / five8 / bs58) |
| JSON/log pipelines where Base58 is a measurable hotspot | Cold paths where stdlib-adjacent deps already fine |
| Append* into reused buffers | EncodeCached32 without reading collision caveats for adversarial inputs |
Related
Summary
fluxrpc/base58 is a dependency-light Go Base58 codec optimized for Solana’s 32/64-byte encode storm: fixed-size kernels, AVX2 when available, zero-alloc append/decode paths, batch and optional key interning. Drop it into Go services that mint Base58 strings at RPC scale; keep verifying benches on your CPU and prefer AppendEncode* in the hottest loops.
Keep reading
Hosted head-slot RPC without credit gimmicks — and a self-hostable path that shoots txs at N+1 leaders over QUIC/UDP.
Agave v4.2 branch cut June 29. Mainnet general adoption August 10. The headline: Anza is targeting 200ms slot times. The breaking changes: XDP transmit on by default (needs CAP_NET_ADMIN), blockstore legacy format removed, confidential transfer JSON fields renamed, vote-account CLI output changed.
Benchmarked: Solana Foundation public RPC, Extrnode, and PublicNode across getSlot, getLatestBlockhash, getBalance, getEpochInfo, getSignaturesForAddress, and getTokenAccountsByOwner. The basic methods are similar. The indexing-heavy methods diverge by 4.5×. Published RPC 2.0 numbers push that to 24×.
Get new articles in your inbox
Technical deep-dives on Solana tooling, infrastructure, and ecosystem. No noise.
