Skip to content

feat: add RIP-7212 P-256 (secp256r1) precompile at 0x100#4030

Open
Kailai-Wang wants to merge 1 commit into
devfrom
feat/p256-precompile-rip7212
Open

feat: add RIP-7212 P-256 (secp256r1) precompile at 0x100#4030
Kailai-Wang wants to merge 1 commit into
devfrom
feat/p256-precompile-rip7212

Conversation

@Kailai-Wang
Copy link
Copy Markdown
Collaborator

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#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/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

  • 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 p256verify. 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 callers and intentionally differs from ed25519's error-on-bad-length. Low-s is not enforced (per RIP-7212 / FIPS-186-5).
  • Registered at AddressU64<256> with EthereumPrecompilesChecks (allows DELEGATECALL, like the 0x01–0x09 eth-spec precompiles). Stateless + caller-agnostic, so delegatecall is safe and maximizes caller compatibility (4337/daimo-style). 256 is already inside the (1, 20565) range — no range / used_addresses change needed.
  • spec_version left 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. 0x100 is 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):

  • 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.

Builds: heima + paseo runtime wasm (no_std) and the node — all clean (confirms p256/std feature gating is correct; wasm compiles with it off).

Live on a local dev chain (heima-node --dev, chainId 2013):

call result
valid vector → 0x100 (direct eth_call) 0x…01
invalid (flipped r) 0x empty ✅
via Solidity staticcall caller — verify(valid) true
verify(invalid) false
verifyRaw(wrong-length 32B) 0x empty, no revert
total tx gas to 0x100 ~27.5k (vs ~707k for the Solidity verifier)

Files

  • parachain/precompiles/p256/{Cargo.toml,src/lib.rs,src/tests.rs} (new)
  • parachain/Cargo.toml (workspace member + dep), parachain/Cargo.lock
  • parachain/runtime/{heima,paseo}/Cargo.toml (dep + std), …/src/precompiles.rs (import + 0x100 entry)

@linear
Copy link
Copy Markdown

linear Bot commented Jun 2, 2026

P-256

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
heima-aa-demo-app Ignored Ignored Jun 2, 2026 3:29pm

Request Review

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.
@Kailai-Wang Kailai-Wang force-pushed the feat/p256-precompile-rip7212 branch from b137136 to 96720ea Compare June 2, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant