feat: add RIP-7212 P-256 (secp256r1) precompile at 0x100#4030
Open
Kailai-Wang wants to merge 1 commit into
Open
feat: add RIP-7212 P-256 (secp256r1) precompile at 0x100#4030Kailai-Wang wants to merge 1 commit into
Kailai-Wang wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Adds a native secp256r1 (P-256) ECDSA signature-verification precompile at the RIP-7212 standard address 0x100, on both the heima and paseo runtimes. Motivation (litentry/agentKeys#170): the only way to verify a P-256 / WebAuthn passkey signature on Heima today is the pure-Solidity daimo P256Verifier, which costs ~707k gas per verify. A native precompile drops this to the RIP-7212 flat 3450 gas (~200x), unblocking the agentKeys passkey / ERC-4337 on-chain auth path. Implementation: - New crate parachain/precompiles/p256 (pallet-evm-precompile-p256). Own implementation over the RustCrypto `p256` crate (already a workspace dep, used by primitives/teebag sgx_verify); not derived from Moonbeam's GPL crate. Structurally mirrors Frontier's ed25519 precompile but implements `Precompile` directly (flat cost) and follows RIP-7212 semantics. - RIP-7212 I/O: 160-byte input (hash||r||s||x||y) -> 32-byte uint256(1) on valid, EMPTY output on invalid signature OR malformed input (never reverts). This matches the deployed daimo verifier's callers and intentionally differs from ed25519's error-on-bad-length behavior. Low-s is NOT enforced (per spec). - Registered at AddressU64<256> with EthereumPrecompilesChecks (allow DELEGATECALL, like the eth-spec precompiles) — stateless + caller-agnostic, so delegatecall is safe and maximizes caller compatibility. 256 is already within the (1, 20565) range; no range/used_addresses change needed. spec_version intentionally left at 9261 (on-chain is 9260; this ships in the same 9261 runtime-upgrade release as the other pending changes). On-chain activation is a separate governance step. Verification (all green): - Rust unit tests (parachain/precompiles/p256/src/tests.rs): canonical Wycheproof valid vector verifies; flipped r / flipped pubkey-x fail; wrong lengths {0,96,159,161,320} and all-zero 160B fail; handle path records exactly 3450 gas and returns uint256(1) / empty as specified. - Built heima + paseo runtime wasm (no_std) and the node, all clean. - Live on a local dev chain: direct eth_call to 0x100 and via a Solidity staticcall caller — valid -> 0x..01, invalid -> empty, wrong-length -> empty with NO revert. Total tx gas ~27.5k vs ~707k for the Solidity verifier. Related to litentry/agentKeys#170.
b137136 to
96720ea
Compare
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.
What
Adds a native secp256r1 (P-256) ECDSA signature-verification precompile at the RIP-7212 standard address
0x100, registered on both the heima and paseo runtimes.Why —
Related to litentry/agentKeys#170The only way to verify a P-256 / WebAuthn passkey signature on Heima today is the pure-Solidity daimo
P256Verifier, which costs ~707k gas/verify (measured on Heima mainnet, per agentKeys#170). A native precompile drops this to the RIP-7212 flat 3450 gas (~200×), unblocking the agentKeys passkey / ERC-4337 on-chain master-auth path (agentKeys#163/#164).Implementation
parachain/precompiles/p256(pallet-evm-precompile-p256). Own implementation over the RustCryptop256crate (already a workspace dep — used byprimitives/.../teebag/sgx_verify). Not derived from Moonbeam's GPLp256verify. Structurally mirrors Frontier'sed25519precompile, but implementsPrecompiledirectly (flat cost) and follows RIP-7212 semantics.hash || r || s || x || y→ 32-byteuint256(1)on valid; empty output on invalid signature OR malformed input (never reverts). This matches the deployed daimo-verifier callers and intentionally differs fromed25519's error-on-bad-length. Low-s is not enforced (per RIP-7212 / FIPS-186-5).AddressU64<256>withEthereumPrecompilesChecks(allowsDELEGATECALL, like the 0x01–0x09 eth-spec precompiles). Stateless + caller-agnostic, so delegatecall is safe and maximizes caller compatibility (4337/daimo-style).256is already inside the(1, 20565)range — no range /used_addresseschange needed.spec_versionleft at 9261 on purpose (on-chain is 9260; this rides the same 9261 runtime-upgrade release as the other pending changes). On-chain activation is a separate governance step (authorizeUpgrade/setCode) — this PR only ships code.Why 0x100 (standards-compliant)
Heima already places Ethereum-standard precompiles at their canonical addresses (0x01–0x09) and reserves high ranges (1024+, 20480+) for non-standard/Litentry-specific ones.
0x100is the RIP-7212 / EIP-7951 canonical P256VERIFY address — aligning with it keeps callers (and the daimo-verifier migration path, Moonbeam, a future Ethereum P256VERIFY) drop-in compatible.Verification (all green)
Rust unit tests (
parachain/precompiles/p256/src/tests.rs,cargo test -p pallet-evm-precompile-p256→ 7 passed):r/ flipped pubkey-x→ fail;{0,96,159,161,320}and all-zero 160B → fail;uint256(1)/ empty as specified.Builds: heima + paseo runtime wasm (no_std) and the node — all clean (confirms
p256/stdfeature gating is correct; wasm compiles with it off).Live on a local dev chain (
heima-node --dev, chainId 2013):0x100(directeth_call)0x…01✅0xempty ✅staticcallcaller —verify(valid)true✅verify(invalid)false✅verifyRaw(wrong-length 32B)0xempty, no revert ✅Files
parachain/precompiles/p256/{Cargo.toml,src/lib.rs,src/tests.rs}(new)parachain/Cargo.toml(workspace member + dep),parachain/Cargo.lockparachain/runtime/{heima,paseo}/Cargo.toml(dep + std),…/src/precompiles.rs(import + 0x100 entry)