chore(deps): begin ethers→alloy migration (phases 1-3)#368
Open
clawdbot-glitch003 wants to merge 4 commits into
Open
chore(deps): begin ethers→alloy migration (phases 1-3)#368clawdbot-glitch003 wants to merge 4 commits into
clawdbot-glitch003 wants to merge 4 commits into
Conversation
Phase 1: add alloy 1.0 (resolved to 1.8.3) alongside ethers in lit-api-server and bump the unused alloy declaration in lit-core workspace deps from 0.12.5 with the feature set needed by later phases (contract, dyn-abi, eip712, network, providers, rpc-types, signer-local, sol-types). Phase 2: migrate the two truly-leaf keccak256 sites to alloy::primitives::keccak256 — src/dstack/v1/mod.rs and src/core/v1/guards/billing_auth.rs. The other "leaf" candidates all have ethers types (H160/U256/ProviderError) in their public API and have been bundled into phase 4 alongside the signer work where they belong. Phase 3: migrate src/core/eip712.rs to alloy::dyn_abi::TypedData for digest computation and alloy::primitives::Signature:: recover_address_from_prehash for recovery. Schema introspection uses a local TypedDataSchemaView deserialized from the same JSON since alloy's Resolver has private fields. A cross-impl parity test confirms an ethers-signed payload verifies byte-identically under the alloy verifier — that test will be deleted in phase 7 when ethers is dropped. Callers bridged at two sites (billing_auth.rs, account_management.rs::convert_to_chain_secured_account). Motivation and full phase plan in plans/ethers-to-alloy.md. Clears 5 of the 9 vulnerability-class advisories currently ignored in deny.toml (RUSTSEC-2023-0071, 2026-0049/0098/0099/0104) once phase 7 lands. 227/227 lib tests pass; clippy clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…lloy-migration # Conflicts: # lit-api-server/Cargo.lock
CI fmt check picked up two small formatting drifts in the Phase 3 file — the Signature parse chain indentation and an out-of-order use statement in the ethers cross-impl parity test. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codex challenge mode flagged 4 actionable findings on the Phase 3
EIP-712 verifier; this commit addresses all of them.
1. (High) `Eip712FieldDef` now uses `#[serde(deny_unknown_fields)]`.
A phishing dApp could otherwise embed a decoy key like
`{ "name": "address", "type": "address", "label": "Approve $500" }`.
The EIP-712 type hash only commits to `(name, type)`, so the
digest still recovers and the canonical schema-equality check
would silently pass — meanwhile some wallet UIs surface the
unknown metadata to the user. The old ethers `Eip712DomainType`
had this guard; the new local struct was missing it.
2. (Medium) `DomainView` now uses `#[serde(deny_unknown_fields)]`.
The pre-existing `verifying_contract`/`salt` checks only fired
for those two specific keys; an arbitrary key like `displayName`
would slip through. Same anti-phishing rationale as (1).
3. (Medium) `verify_eip712_signature` now accepts both wire shapes:
the typed-data object inline AND a JSON string containing it
(what `eth_signTypedData_v4` returns from MetaMask). Both ethers'
and alloy's `TypedData::Deserialize` accept both shapes, so the
prior verifier handled both; rejecting the stringified form here
would have silently broken any client forwarding the wallet
response verbatim.
4. (Medium) `extract_issued_at` now parses `issuedAt` as `uint256`
(decimal string / hex string / JSON number) matching the field
declaration, then bounds-checks against `i64::MAX` before
downcasting. The previous decimal-i64 parser would have rejected
hex strings pre-recovery even though alloy's digest computation
accepts them, silently breaking otherwise-valid signatures.
Also adds `TypedDataSchemaView`-level `deny_unknown_fields` so
unknown top-level keys (e.g. `displayMessage` alongside `domain`/
`types`/`primaryType`/`message`) are rejected — small defense-in-
depth gain.
5 new tests cover each fix:
- rejects_extra_key_in_field_def
- rejects_extra_key_in_domain
- accepts_stringified_typed_data
- accepts_hex_issued_at
- rejects_issued_at_overflows_i64
The existing `rejects_i64_min_issued_at` test was retargeted: the
i64::MIN payload is now rejected one layer earlier (uint256 can't
hold negatives) before reaching `validate_timestamp`, so the
assertion checks for "issuedAt" in the error rather than
"timestamp". The i128 widening in `validate_timestamp` stays as
defense in depth.
Codex's 5th finding (Low/compat) — alloy rejects non-standard v
values 2..26 and 29..34 that ethers normalized — is acceptable
strictness, not addressed here.
Test result: 24/24 eip712 tests pass; 217/217 lib tests; clippy +
fmt clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First slice of the ethers → alloy migration. Adds alloy 1.0 (resolved to 1.8.3) alongside ethers and ports the low-risk EIP-712 verification path. See
plans/ethers-to-alloy.mdfor the full 7-phase plan; this PR lands phases 1-3.Why now:
ethersis unmaintained and accounts for 5 of the 9 vulnerability-class advisories currently ignored indeny.toml(RUSTSEC-2023-0071,2026-0049,0098,0099,0104). Those entries can be removed once Phase 7 lands.Phase 1 — Add alloy alongside ethers
lit-coreworkspace depalloy 0.12.5→1.0(it was declared but unused — pre-staged for this work)alloy = "1.0"tolit-api-server/Cargo.tomlwith features needed by later phases:contract,dyn-abi,eip712,network,providers,rpc-types,signer-local,sol-typesPhase 2 — Leaf util migrations (keccak256-only)
Only the two truly-leaf keccak256 sites swap to
alloy::primitives::keccak256:src/dstack/v1/mod.rssrc/core/v1/guards/billing_auth.rsEvery other "leaf" candidate (
utils/mod.rs,parse_with_hash.rs,curve_type.rs,api_status.rs,blockchain_cache.rs,decode_revert.rs) has ethers types in its public API and so migration must be batched with callers — they've been moved into Phase 4 alongside the signer/provider work where they bundle naturally.Phase 3 — EIP-712
src/core/eip712.rsported toalloy::dyn_abi::TypedDatafor digest computation andalloy::primitives::Signature::recover_address_from_prehashfor recoveryTypedDataSchemaViewdeserialized from the same JSON (alloy'sResolverhas private fields, so we can't inspect it directly)cross_impl_parity_ethers_signed_verifies_under_alloytest signs canonical TypedData with ethersLocalWalletand verifies it under the alloy verifier — confirms byte-identical digest between the two libs. The test is removed in Phase 7 along with the ethers dep.wallet.as_bytes()→wallet.as_slice()inbilling_auth.rs; oneH160::from_slice(signer.as_slice())bridge inaccount_management.rs::convert_to_chain_secured_account(the other twoverify_eip712_signaturecall sites onlyDebug-print the recovered signer — no bridge needed)src/accounts/signable_contract.rshad been listed under Phase 3 but contains zero EIP-712 code — it's pureSignerMiddleware/NonceManagerMiddleware/ContractCall. Moved into Phase 4.Test plan
cargo checkclean inlit-api-server(post-merge withorigin/next)cargo test --lib— 227/227 pass (including new cross-impl parity test)cargo clippy --lib --testscleancore::eip712::tests::*passRisk
Low for this PR. The cross-impl parity test pins that an ethers-signed payload still verifies under alloy, so existing client signatures continue to work unchanged. The higher-risk signer/provider/middleware work is deferred to Phase 4.
🤖 Generated with Claude Code