gmajor/sr25519-bindings
PHP bindings for sr25519 cryptography, enabling Substrate/Polkadot-compatible key handling, signing, and signature verification in PHP and Laravel applications through a simple native-backed API.
gmajor/sr25519-bindings is a thin PHP wrapper around a native Rust implementation of sr25519 — the Schnorrkel/Ristretto signature scheme used by Substrate-based chains (Polkadot, Kusama, and most parachains). You reach for it when your Laravel app needs to generate sr25519 keypairs, sign payloads, or verify signatures produced by a Substrate wallet or service. It is not a chain client; it does no RPC, no SS58 encoding, no mnemonic-to-address UI. It is the cryptography only.
Minimal setup:
composer require gmajor/sr25519-bindings
ext-ffi must be loaded and enabled. In php.ini: ffi.enable=true for local dev, ffi.enable=preload in production if you can preload. Confirm with php -r "var_dump(extension_loaded('ffi'));".vendor/gmajor/sr25519-bindings for the shipped .so/.dylib/.dll and that the architecture (x86_64 vs arm64, glibc vs musl) matches your runtime. This is the single most common reason the package "doesn't work."use Sr25519\Sr25519;
$sr = new Sr25519();
$pair = $sr->generateKeypair(); // ['secretKey' => ..., 'publicKey' => ...]
$sig = $sr->sign('hello', $pair['secretKey']);
var_dump($sr->verify($sig, 'hello', $pair['publicKey'])); // true
Treat the exact method names as something to verify against vendor/gmajor/sr25519-bindings/src — the repo is small (2 stars, 0 dependents, last release 2024-10-15) and the README is the only spec you get.
First real use case: "Sign in with Polkadot." A user connects a wallet, your app issues a nonce, the wallet signs it, you verify with this package, and you log them in. That one flow exercises key generation for tests, verification, encoding discipline, and error handling — everything you need to know about living with this package.
Bind it once, hide it behind a contract. Never let new Sr25519() leak into controllers. Create app/Contracts/Sr25519Signer.php with generate(), sign(), verify(), publicKeyFromSecret(), and bind a concrete adapter in a service provider as a singleton. If the native library ever breaks on a PHP upgrade, you swap one class — or move to an HTTP sidecar — without touching business logic.
// app/Providers/Sr25519ServiceProvider.php
$this->app->singleton(Sr25519Signer::class, fn () => new NativeSr25519Signer(
config('sr25519.library_path')
));
Wallet login (nonce challenge). Issue a random challenge, store it in the session or a login_challenges table with an expiry, and verify the returned signature against the public key the user claims. Bind the verified public key to the user record, then invalidate the challenge. Keep the signed message byte-identical to the one you issued — whitespace, newlines, and a 0x prefix mismatch are the top cause of "valid signature rejected."
Verify inbound payloads, don't trust the transport. If a Substrate-based service or relayer posts to a webhook, have it include publicKey, signature, and the raw body, and verify the signature over the raw request body before json_decode(). Re-encoding JSON and then verifying will fail.
Server-side signing for relayers and custodial flows. Generate keypairs server-side only when you must, store the secret with Laravel's encrypted cast or in a KMS, and pull it via a config value rather than a model attribute you might accidentally serialize. Sign in a queued job when throughput matters; each call is fast, but a loop over thousands of items will pin a worker.
Derivation for per-user addresses. sr25519 supports soft and hard junctions for deriving child keypairs from a parent. Use it to give each user a deterministic deposit address from one root seed. Write a test against a known vector (the well-known //Alice dev account is the standard) before trusting your junction syntax.
Testing. Fake the contract in feature tests so CI never needs FFI or the native library:
$this->mock(Sr25519Signer::class, fn ($m) => $m
->shouldReceive('verify')->andReturn(true));
Keep a small @requires extension ffi test suite with fixed keys and real signatures to catch native-library regressions on deploy. Run those against a staging image, not the dev laptop only.
Validation. Add a HexSignature rule (64 bytes / 128 hex chars) and a PublicKey rule (32 bytes / 64 hex chars) so malformed input is rejected by the validator rather than reaching FFI.
php:8.x-fpm images, many managed hosts, and hardened production php.ini disable it. Add docker-php-ext-install ffi (or the equivalent) plus ffi.enable=1 to your image, and assert it at boot in a health check so you fail loudly instead of at first login..so often disagree. If you see a load failure on Alpine, either switch the base image to Debian-slim or build the Rust library yourself for the musl target.artisan. Use base_path() or __DIR__, and put the path in config/sr25519.php so you can override per environment.bin2hex/hex2bin. Strip 0x prefixes in one place. Half the bugs in this space are 0x-prefix and case-sensitivity bugs.5Grwva… requires base58 + the Blake2b checksum, done separately. Don't expect getPublicKey() to return an address your users recognize.dd() a keypair, and scrub request payloads containing signatures or secrets in Handler::context().composer.lock, commit the matching native library into your deploy artifact, and add a post-deploy smoke test that signs and verifies a fixed string.Sr25519Signer interface with the package as one implementation and, if it ever becomes unmaintained, a Rust sidecar exposing /sign and /verify as the second. Your application code won't notice the difference.How can I help you explore Laravel packages today?