Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Secp256K1 Laravel Package

kornrunner/secp256k1

A PHP secp256k1 elliptic curve library for key generation, ECDSA signing and verification, and public key recovery. Useful for Ethereum-style cryptography without native extensions.

Deep Wiki
Context7

Getting Started

Install and prerequisites. composer require kornrunner/secp256k1. Recent releases require PHP 8.0+ and the GMP extension — verify with php -m | grep gmp before anything else. There is no OpenSSL/FFI dependency; everything is pure PHP on top of GMP.

Where to look first. Open the README, then jump straight to src/Secp256k1.php and the Signature class. That's the whole surface area — a handful of methods for sign, verify, recoverPublicKey, and getPublicKey. Skim tests/ to see exact input/output formats (hex vs. raw binary strings), which is the single biggest source of confusion.

First use case — key derivation and signing. The canonical "hello world" is: take a 32-byte private key, derive the public key, sign a 32-byte hash, and verify. Do this before wiring it into anything else:

use kornrunner\Secp256k1;

$secp = new Secp256k1();

$privateKey = str_repeat('01', 32);            // 32-byte raw hex
$publicKey  = $secp->getPublicKey($privateKey); // uncompressed hex (04...)

$message   = 'deadbeef...';                    // 32-byte hash, hex
$signature = $secp->sign($message, $privateKey);

$valid = $secp->verify($message, $signature, $publicKey);

If that round-trip returns true, your environment and formats are correct and you can move on to real integration.

First realistic integration. This package is most often reached for when signing Ethereum transactions or EIP-191/712 messages in PHP. Pair it with kornrunner/keccak for hashing and treat the signature's r, s, v as the handoff format to your transaction encoder.


Implementation Patterns

Sign a pre-hashed payload, never raw data. sign() expects the 32-byte digest, not the original message. Hash first with keccak256 (Ethereum) or sha256 depending on protocol:

use kornrunner\Keccak;
$hash = Keccak::hash($message, 256); // hex string
$sig  = $secp->sign($hash, $privateKey);

Keep sign → serialize → verify as a single funnel. Because signatures are objects (or r/s/v arrays) rather than opaque blobs, centralize serialization into DER or r||s||v in one helper. Every caller (transaction encoder, API client, message signer) should consume that helper so format drift can't creep in.

Recover the signer rather than trust a passed-in key. For login flows, verification is often "does this signature recover to the expected address?" rather than "does verify() pass?" Use recoverPublicKey($hash, $signature) and compare the resulting address to your stored one — it validates both signature and identity in one step.

Ethereum transaction signing pattern. Build the RLP payload, keccak it, sign, then attach v = recovery + 27 (or + 35 + 2*chainId for EIP-155). The package gives you the recovery id; the chain-id adjustment is your responsibility.

Deterministic output for tests. RFC 6979 deterministic nonces (used by default) mean the same message + key always yields the same signature. That makes unit tests and fixtures stable — commit known-good signature hex into your test suite and assert against it.

Wrap it behind an interface. Define a SignerInterface (or similar) in your app and adapt kornrunner to it. This keeps the pure-PHP implementation swappable for an ext-secp256k1 or web3-based implementation later without touching call sites. It also isolates the hex/binary format quirks to one class.

Batch validation for API endpoints. If you're verifying many signatures per request, construct one Secp256k1 instance and reuse it — it's stateless and safe to reuse — rather than instantiating per call.

Derive addresses by pairing with keccak. getPublicKey() returns the public key; for an Ethereum address, drop the 04 prefix, keccak the remaining 64 bytes, and take the last 20 bytes. This is the standard companion pattern and worth putting in a small utility function once.


Gotchas and Tips

Pure PHP is slow. Every scalar multiplication touches GMP. Signing is on the order of milliseconds-to-tens-of-milliseconds and verification is slower still. Don't put this in a hot request path that verifies dozens of signatures per user action; cache verification results, and consider native implementations if throughput matters.

GMP is not optional and not always installed. The failure mode is a confusing Call to undefined function gmp_init() (or the extension silently missing in a slim Docker image). Add ext-gmp to your composer.json and CI setup so it fails loudly at install time.

Hex vs. binary strings. The API accepts/returns hex strings in some places and raw binary in others, and this changes between versions. Always consult the tests for the exact version you installed. When in doubt, call with hex and inspect the output type explicitly rather than assuming.

Low-S normalization and recovery id. Ethereum requires the low-S form of s; if you get signatures that verify locally but are rejected by nodes, you're likely missing the normalization or the v adjustment. Don't hand-compute v — get it from the signature object and adjust from there.

Hash before signing — this is the #1 mistake. Passing a raw message to sign() produces a signature that will not verify against the same message through any standard tooling. If verification "works in your code but not with Ethereum tools," check hashing and message encoding first.

Constant-time is not a guarantee you should assume. Being pure PHP with GMP means timing behavior is implementation-dependent. For high-value keys, prefer a native library or HSM; use this package for convenience, test fixtures, and moderate-value flows rather than custody of large funds.

Extension points are thin by design. The package has no plugin/configuration system worth speaking of. Your extension surface is: wrap Secp256k1 in your own class, override formatting/serialization there, and inject dependencies (hasher, address encoder) rather than trying to subclass internals. Because the class is small, wrapping is cheap and far more maintainable than monkey-patching.

Version pin carefully. With a 2025 release and modest adoption (39 stars), the API can shift between minor versions. Pin the exact version in composer.json, and run the upstream test suite as a smoke test after every update — the README may lag behind the code.

Debugging strategy. When signatures fail, check in this order: (1) is input hex the right length and casing? (2) was the message hashed? (3) is s in low-S form? (4) does the public key include the 04 prefix, and is your code stripping/adding it consistently? (5) does a minimal round-trip (getPublicKey → sign → verify) pass with the same key and hash? Most issues resolve at step 1 or 3.

Use it as a reference implementation. Because it's small and readable, keep the source around even if you later swap to a native library — walking through sign() and recoverPublicKey() is the fastest way to understand what your production signer actually does.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package