Skip to content

Commit d86527e

Browse files
Kemperino2xic
andauthored
Add Fusaka: secp256r1 precompile + CLZ opcode (#400)
* add new precompile and evm version * add CLZ --------- Co-authored-by: Brage <[email protected]>
1 parent f8f4bb0 commit d86527e

File tree

10 files changed

+236
-134
lines changed

10 files changed

+236
-134
lines changed

context/ethereumContext.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const postMergeHardforkNames: Array<string> = [
6565
'shanghai',
6666
'cancun',
6767
'prague',
68+
'osaka',
6869
]
6970
export const prevrandaoDocName = '44_merge'
7071
const EOF_EIPS = [
@@ -538,12 +539,14 @@ export const EthereumProvider: React.FC<{}> = ({ children }) => {
538539

539540
const _loadPrecompiled = () => {
540541
const precompiled: IReferenceItem[] = []
542+
const meta = PrecompiledMeta as IReferenceItemMetaList
541543

542544
const addressIterator = getActivePrecompiles(common).keys()
543545
let result = addressIterator.next()
544546
while (!result.done) {
545-
const meta = PrecompiledMeta as IReferenceItemMetaList
546-
const addressString = '0x' + result.value.slice(-2)
547+
// Convert full address (e.g., "0000...0001" or "0000...0100") to short form
548+
// Format: 0x01-0x0f (2 digits), 0x10-0x11 (2 digits), 0x100 (3 digits)
549+
const addressString = '0x' + result.value.slice(2).replace(/^0+(?=..)/, '')
547550

548551
if (!meta[addressString]) {
549552
result = addressIterator.next()

docs/opcodes/1E.mdx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
fork: Osaka
3+
group: Comparison & Bitwise Logic Operations
4+
---
5+
6+
*Index 1 is top of the stack.*
7+
8+
## Notes
9+
10+
Counts the number of leading zero bits in a 256-bit word. If the input is zero, the result is 256. This operation is useful for efficient implementation of mathematical functions (sqrt, cbrt, log2), byte/word size comparisons, and bitmap operations.
11+
12+
This opcode is specified in [EIP-7939](https://eips.ethereum.org/EIPS/eip-7939).
13+
14+
## Stack input
15+
16+
0. `x`: 256-bit value to count leading zeros of.
17+
18+
## Stack output
19+
20+
0. `clz(x)`: the number of leading zero bits in `x` (0 to 256).
21+
22+
## Examples
23+
24+
| * | Input | Output |
25+
|--:|------:|-------:|
26+
| `1` | `0x0000000000000000000000000000000000000000000000000000000000000000` | `0x100` |
27+
28+
| * | Input | Output |
29+
|--:|------:|-------:|
30+
| `1` | `0x8000000000000000000000000000000000000000000000000000000000000000` | `0x0` |
31+
32+
| * | Input | Output |
33+
|--:|------:|-------:|
34+
| `1` | `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` | `0x0` |
35+
36+
| * | Input | Output |
37+
|--:|------:|-------:|
38+
| `1` | `0x4000000000000000000000000000000000000000000000000000000000000000` | `0x1` |
39+
40+
| * | Input | Output |
41+
|--:|------:|-------:|
42+
| `1` | `0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff` | `0x1` |
43+
44+
| * | Input | Output |
45+
|--:|------:|-------:|
46+
| `1` | `0x0000000000000000000000000000000000000000000000000000000000000001` | `0xff` |
47+
48+
[Reproduce in playground](/playground?fork=osaka&unit=Wei&codeType=Mnemonic&code='PUSH32%200x7zzz%5CnCLZ'~fffffffz~~~%01z~_).
49+
50+
## Error cases
51+
52+
The state changes done by the current context are [reverted](#FD) in those cases:
53+
- Not enough gas.
54+
- Not enough values on the stack.

docs/precompiled/0x100.mdx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
---
2+
fork: Osaka
3+
---
4+
5+
## Notes
6+
7+
P256VERIFY performs ECDSA signature verification over the secp256r1 elliptic curve (also known as P-256 or prime256v1). This curve is a NIST-standardized curve widely supported in modern secure hardware including Apple Secure Enclave, Android Keystore, HSMs, TEEs, and FIDO2/WebAuthn authenticators.
8+
9+
Native secp256r1 support enables sophisticated account abstraction patterns like device-native signing, multi-factor authentication, and simplified key management - reducing friction for mainstream adoption through familiar authentication flows.
10+
11+
This precompile is specified in [EIP-7951](https://eips.ethereum.org/EIPS/eip-7951) and supersedes RIP-7212 by implementing the same functionality with the same interface, but with critical security fixes:
12+
- **Point-at-infinity check**: Ensures the recovered point R' is not the point at infinity, preventing non-deterministic behavior.
13+
- **Modular comparison**: Uses `r' ≡ r (mod n)` instead of `r' == r` to handle cases where the x-coordinate exceeds the curve order.
14+
15+
## Inputs
16+
17+
| Byte range | Name | Description |
18+
|-----------:|-----:|------------:|
19+
| `[0; 31]` (32 bytes) | hash | Message hash to verify |
20+
| `[32; 63]` (32 bytes) | r | Signature component r, must satisfy `0 < r < n` |
21+
| `[64; 95]` (32 bytes) | s | Signature component s, must satisfy `0 < s < n` |
22+
| `[96; 127]` (32 bytes) | qx | Public key x-coordinate, must satisfy `0 ≤ qx < p` |
23+
| `[128; 159]` (32 bytes) | qy | Public key y-coordinate, must satisfy `0 ≤ qy < p` |
24+
25+
Input must be exactly **160 bytes**. All values are encoded as big-endian unsigned integers.
26+
27+
### Input Validation
28+
29+
The precompile performs the following validation checks:
30+
- Input length must be exactly 160 bytes
31+
- Signature components `r` and `s` must be in range `(0, n)`
32+
- Public key coordinates must be valid field elements `[0, p)`
33+
- The point `(qx, qy)` must satisfy the curve equation `qy² ≡ qx³ + ax + b (mod p)`
34+
- The point `(qx, qy)` must not be the point at infinity
35+
36+
### Curve Parameters
37+
38+
| Parameter | Value |
39+
|----------:|------:|
40+
| Field modulus (p) | `0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff` |
41+
| Subgroup order (n) | `0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551` |
42+
| Coefficient a | `0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc` |
43+
| Coefficient b | `0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b` |
44+
45+
## Output
46+
47+
| Byte range | Name | Description |
48+
|-----------:|-----:|------------:|
49+
| `[0; 31]` (32 bytes) | success | `0x0000000000000000000000000000000000000000000000000000000000000001` for valid signatures |
50+
51+
Returns empty output (``) for:
52+
- Invalid input length
53+
- Invalid field element encoding
54+
- Invalid signature component bounds
55+
- Invalid public key (not on curve or point at infinity)
56+
- Signature verification failure
57+
58+
## Example
59+
60+
| Input | Output |
61+
|------:|-------:|
62+
| `0xbb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca6050232ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e184cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd762927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838c7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e` | `0x0000000000000000000000000000000000000000000000000000000000000001` |
63+
64+
The example above breaks down as:
65+
- **hash**: `0xbb5a52f42f9c9261ed4361f59422a1e30036e7c32b270c8807a419feca605023`
66+
- **r**: `0x2ba3a8be6b94d5ec80a6d9d1190a436effe50d85a1eee859b8cc6af9bd5c2e18`
67+
- **s**: `0x4cd60b855d442f5b3c7b11eb6c4e0ae7525fe710fab9aa7c77a67f79e6fadd76`
68+
- **qx**: `0x2927b10512bae3eddcfe467828128bad2903269919f7086069c8c4df6c732838`
69+
- **qy**: `0xc7787964eaac00e5921fb1498a60f4606766b3d9685001558d1a974e7341513e`
70+
71+
## Gas
72+
73+
The gas cost is fixed at **6900** gas. This cost is based on benchmarking against the existing ECRECOVER precompile (3000 gas). If the input does not allow computation of a valid result, all the gas sent is consumed.

docs/precompiled/0x100/osaka.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Gas
2+
3+
static_gas = 6900
4+
dynamic_gas = 0
5+
6+
The gas cost is fixed at 6900 gas based on benchmarking against the ECRECOVER precompile (3000 gas). The actual cost of secp256r1 verification was found to be significantly higher than secp256k1 recovery.
7+
8+
If the input does not allow computation of a valid result (invalid inputs or verification failure), all the gas sent is consumed.

opcodes.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,9 @@
160160
"description": "Arithmetic (signed) right shift operation"
161161
},
162162
"1e": {
163-
"input": "",
164-
"output": "",
165-
"description": ""
163+
"input": "x",
164+
"output": "clz(x)",
165+
"description": "Count leading zero bits"
166166
},
167167
"1f": {
168168
"input": "",

package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
"@babel/core": "^7.0.0",
2020
"@emotion/react": "^11.11.3",
2121
"@emotion/styled": "^11.11.0",
22-
"@ethereumjs/block": "^10.0.0",
23-
"@ethereumjs/common": "^10.0.0",
24-
"@ethereumjs/evm": "^10.0.0",
25-
"@ethereumjs/tx": "^10.0.0",
26-
"@ethereumjs/util": "^10.0.0",
27-
"@ethereumjs/vm": "^10.0.0",
22+
"@ethereumjs/block": "^10.1.0",
23+
"@ethereumjs/common": "^10.1.0",
24+
"@ethereumjs/evm": "^10.1.0",
25+
"@ethereumjs/tx": "^10.1.0",
26+
"@ethereumjs/util": "^10.1.0",
27+
"@ethereumjs/vm": "^10.1.0",
2828
"@kunigi/string-compression": "1.0.2",
2929
"@mdx-js/loader": "^2.3.0",
3030
"@mdx-js/react": "^2.3.0",

0 commit comments

Comments
 (0)