amashukov/ton-crypto-php
Correct TON mnemonic and Ed25519 keypair derivation in pure PHP. Uses PBKDF2-HMAC-SHA512 with the "TON default seed" salt, not BIP-39, so 24-word phrases produce matching TON wallets. Sign and verify via libsodium, zero composer dependencies.
Architecture fit. This is a leaf utility, not a platform component. It occupies exactly one layer of a TON integration — mnemonic → seed → Ed25519 keypair, plus sign/verify — and deliberately stops there. It does not derive addresses, build cells, or talk to the chain. That means it is not adoptable on its own: the real unit of commitment is the four-package set (ton-crypto-php, ton-cell-php, ton-wallet-php, toncenter-client-php), and this package is the smallest and least substitutable piece of it. From a product standpoint, the value proposition is narrow but real: it encodes a domain rule (TON's PBKDF2-HMAC-SHA512 pipeline with salt "TON default seed", 100k iterations, plus a pre-HMAC pass) that generic BIP-39 libraries get silently wrong. The package is essentially a correctness shim for a well-known footgun.
Integration feasibility. Composer install is trivial and the dependency graph is empty, so there is no version-conflict surface and no transitive risk to audit. The binding constraints are infrastructure, not code: PHP 8.3+ and ext-sodium. Laravel apps still on 8.1/8.2 are blocked outright, and slim Docker images frequently ship without sodium. The public API is four surfaces (Mnemonic::toSeed, Mnemonic::toKeyPair, KeyPair::generate/fromSeed, sign/verify) — a competent Laravel developer can read the whole thing in under an hour. Effort to integrate is not the constraint; effort to integrate safely is.
Technical risk. Three risks dominate, in order:
toKeyPair(). An arbitrary string appears to yield some 32-byte seed rather than throwing. Wired naively into a "restore wallet" form, a typo mints an unrecoverable wallet and the failure surfaces weeks later as a support escalation.dd(), Telescope, Ray, a queued job's serialised payload, an exception trace — is the application's problem to solve.Key questions for the maintainer / spike.
toKeyPair() validate word count, wordlist membership, or checksum? If not, is validation on the roadmap?$password argument a second factor only?Stack fit. Do not call the static methods from controllers, jobs, or models. The classes are final readonly with static entry points, which makes an owned interface the only viable seam: define TonKeyService (generate / fromMnemonic / sign / verify) returning your own TonKeyMaterial value object, bind it in a service provider, and drive behaviour from config/ton.php (network, wallet_version). This is the single highest-leverage decision in the whole integration: it converts a 0-star dependency into one swappable binding, and it gives you a place to add the input validation the package omits.
Migration path. If the product already touches BIP-39 anywhere — even tangentially, via bitwasp/bitcoin or a generic mnemonic helper — audit every call site before shipping. A TON phrase fed to a BIP-39 library produces a valid-looking keypair and an address the user's wallet will not recognise; that is a data-integrity bug, not a crash. Conversely, seeds derived here are TON-only and must be namespaced as such in storage. For greenfield, there is no migration — just don't let the generic helpers in.
Compatibility. Pin to an explicit version in composer.json and lock it. Verify ext-sodium in the build image, not at runtime — add it to CI and to the Dockerfile (php8.3-sodium on Debian, libsodium-dev at compile time). Keep seed material in a text column (Laravel's Crypt::encryptString() produces a JSON envelope, not raw binary), add seed_ciphertext to model $hidden, and never pass a KeyPair or seed into a queued job constructor — pass a wallet ID and re-derive inside handle().
Sequencing. Four gates, in order: (1) land the crypto package behind TonKeyService with fixture-based tests; (2) add ton-wallet-php and assert a derived address against a wallet you created in official TON tooling — this is the only test that catches a wrong derivation; (3) add ton-cell-php / toncenter-client-php and exercise a read-only path on testnet; (4) enable user-facing wallet creation behind a feature flag, with a shadow-mode comparison against reference output before any real value moves. Do not compress gates 1–2; everything downstream assumes them.
Maintenance. The upside is a very small, auditable, zero-dependency surface — you can read the entire thing and, if necessary, fork it in an afternoon. The downside is that the fork is a realistic plan, not a contingency. Budget for it: vendor the source under app/Support/Ton or a private namespace, pin the version, and record the decision as "we own this code" rather than "we depend on this package." PHPStan L9 and CI reduce the audit cost substantially.
Support. Single maintainer, no SLA, no commercial backing. Route issues through your own team first; treat upstream as a courtesy channel. Document the TON-not-BIP-39 rule in the runbook — it is the root cause of the most likely incident class and it is not obvious to a generalist on-call engineer.
Scaling. The package itself is stateless, network-free, and horizontally trivial. The one cost is CPU: PBKDF2-SHA512 at 100,000 iterations is tens of milliseconds per derivation in pure PHP. That is fine for login and one-off restore, and wrong for bulk import loops. Queue any bulk mnemonic processing, and rate-limit hard any endpoint that accepts a user-supplied phrase — it is a cheap CPU DoS otherwise. Signature verification is fast enough for request-path auth.
Failure modes to instrument. Missing ext-sodium at build (catch in CI, not production); wrong-key silent success from unvalidated mnemonics (catch with wordlist/checksum validation at the form layer plus an address fixture test); seed leakage via logs, Telescope, or job payloads (catch with $hidden, log scrubbing, and a review checklist); nonce replay in TON Connect login (catch with short-TTL cache and Cache::pull); verify() returning false rather than throwing on malformed input (write if (! $ok), never rely on try/catch); binary blobs corrupted by json_encode (base64 at every boundary, raw bytes internally).
Ramp-up. Low. The API is four methods and the README is complete. The real ramp-up cost is domain education — teaching the team that TON derivation is not BIP-39, that the $password argument changes the key entirely rather than acting as a checksum, and that this package gives you keys, not addresses. That is half a day of onboarding and one well-placed document. The library is the easy part; the surrounding key-custody and validation discipline is where the product risk actually lives.
How can I help you explore Laravel packages today?