tuupola/base58
tuupola/base58 is a lightweight PHP library for Base58 encoding and decoding, supporting Bitcoin, Flickr, Ripple, and custom alphabets plus Base58Check. It offers a simple API for compact, human-resistant identifiers in PHP applications.
Install via Composer — there is no Laravel auto-discovery, but nothing needs publishing:
composer require tuupola/base58
Confirm the runtime has one of the math extensions, since that determines your default adapter:
php -m | grep -E 'gmp|bcmath'
Minimal first use — encode a string, decode it back:
use Tuupola\Base58;
$base58 = new Base58(); // Bitcoin alphabet, GMP by default
$encoded = $base58->encode('Hello World!'); // 2NEpo7TZRRrLZSi2U
$decoded = $base58->decode($encoded); // Hello World!
Where to look first:
characters and mode are the only knobs you will touch 95% of the time.Base58::BITCOIN, Base58::FLICKR, Base58::RIPPLE.Base58::GMP, Base58::BCMATH, Base58::PHP.First realistic use case: shortening opaque identifiers (UUIDs, random tokens, SHA-256 digests) into a URL-safe, character-safe representation, or producing Bitcoin-style addresses. Base58 is encoding, not encryption — do not use it to hide secrets.
The class is stateless after construction, so one instance per request is enough. Configure the adapter once at the container level instead of in every call site:
// app/Providers/Base58ServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Tuupola\Base58;
class Base58ServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Base58::class, fn () => new Base58([
'characters' => Base58::BITCOIN,
'mode' => extension_loaded('gmp') ? Base58::GMP : Base58::PHP,
]));
}
}
There is no shipped facade, but you can wrap it in seconds:
namespace App\Support\Facades;
use Illuminate\Support\Facades\Facade;
class Base58 extends Facade
{
protected static function getFacadeAccessor(): string
{
return \Tuupola\Base58::class;
}
}
Prefer constructor injection in application code; keep the facade for Blade and quick scripts.
Resist sprinkling encode()/decode() across controllers. A thin service keeps alphabet and checksum logic in one place:
namespace App\Services;
use Tuupola\Base58;
class ShortId
{
public function __construct(private Base58 $base58) {}
public function encode(string $binary): string
{
return $this->base58->encode($binary);
}
public function decode(string $encoded): string
{
return $this->base58->decode($encoded);
}
}
encode() and decode() are binary-safe, which is the whole point for arbitrary data:
$uuid = (string) \Illuminate\Support\Str::uuid();
$binary = hex2bin(str_replace('-', '', $uuid));
$short = $base58->encode($binary); // ~22 chars instead of 36
For opaque tokens, start from random bytes, not from a string:
$token = base64_encode(random_bytes(16)); // source
$short = $base58->encode(random_bytes(16)); // 22-ish chars, URL safe
The package only handles the alphabet math. Checksum, version byte, and payload are on you:
$payload = "\x00" . $pubKeyHash; // version + hash160
$checksum = substr(hash('sha256', hash('sha256', $payload, true), true), 0, 4);
$address = $base58->encode($payload . $checksum);
Alphabets are not interchangeable — you must decode with the same one you encoded with. Keep the constant next to the caller:
$flickr = new Base58(['characters' => Base58::FLICKR]); // 123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ
$ripple = new Base58(['characters' => Base58::RIPPLE]); // rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz
Bitcoin is the default and the right choice for anything user-facing. Choose Flickr/Ripple only for interoperability with those ecosystems.
Behavior is deterministic for a given (input, alphabet) pair, so golden-vector tests are trivial:
test('base58 roundtrip', function () {
$codec = new Base58(['mode' => Base58::PHP]); // force pure PHP so CI needs no extensions
expect($codec->decode($codec->encode('Hello World!')))->toBe('Hello World!');
});
ext-gmp is absent on a deploy target, construction fails (or silently falls back, depending on version). Pin mode explicitly via extension_loaded('gmp') and prefer BCMath over PHP when possible.Base58::PHP is slow. It's a per-byte long-division implementation — fine for a 32-byte digest, painful for kilobytes of data. Never use it for bulk payloads.utf8mb4_general_ci collation is not — WHERE short_id = ? can match the wrong row. Use a binary collation, VARBINARY, or a BINARY cast, and store the raw bytes column as BLOB/VARBINARY, never VARCHAR.\x00 encodes to a leading 1 (Bitcoin alphabet). Don't ltrim() anything before encoding, and don't strip leading 1s before decoding — the round trip will silently lose data.encode() returns printable ASCII, but decode() returns raw bytes. Be explicit about which side of the boundary you're on — a UTF-8 assumption on the decoded value will corrupt data.Base58Proxy is not a Laravel facade. The package ships a static proxy (Tuupola\Base58Proxy::encode()); don't confuse it with a service-container-bound facade. It's handy in one-off scripts, but it bypasses container config, so avoid it in application code.0 O I l from the Bitcoin alphabet, which makes it good for human-transcribed IDs, but it is not base62 — don't assume it's shorter than base64; it's usually ~5% longer than base64 for the same bytes.characters (any 58-char string) and mode are constructor options, so you can inject alternative alphabets without subclassing. Subclass Base58 only if you need to add checksum/versioning behavior as part of the object.composer.json and cover your own encode/decode round trip with a golden-vector test so a future upgrade that changes defaults shows up as a failing test, not a production incident.bin2hex() and inspect), (2) the alphabet is the default Bitcoin one, (3) the mode matches what the reference implementation used. The mode should not change output, but a wrong alphabet absolutely will.How can I help you explore Laravel packages today?