Skip to content

Commit a0d77ee

Browse files
authored
Merge pull request #5 from 10d9e/feat/impl-precompiles
chore: Release version 0.3.1 with new features and fixes
2 parents 0c6e212 + 61ca42c commit a0d77ee

File tree

4 files changed

+122
-20
lines changed

4 files changed

+122
-20
lines changed

CHANGELOG.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525
### Security
2626
- Nothing yet
2727

28-
## [0.3.0] - 2025-12-20
28+
## [0.3.1] - 2025-12-02
29+
30+
### Added
31+
- **PrecompileId.Custom variant**: Added support for custom precompile identifiers via `PrecompileId.custom("id")`
32+
- **PrecompileId.name() method**: Returns EIP-7910 standardized names for all precompiles (e.g., "SHA256", "BN254_ADD")
33+
- **PrecompileId.precompile() method**: Convenience method to get the appropriate precompile implementation for a given spec, handling spec-specific variants automatically
34+
35+
### Fixed
36+
- **ModExp Osaka gas calculation**: Fixed gas calculation formula to use `max(500, complexity * iteration_count)` instead of `500 + complexity * iteration_count` to match EIP-7883 specification
37+
- **ModExp Osaka complexity calculation**: Fixed to use `max(base_len, mod_len)` instead of `max(base_len, exp_len, mod_len)` for complexity calculation
38+
- **EIP-7823 input size limit**: Corrected ModExp input size limit from 32768 bytes to 1024 bytes per parameter as specified in EIP-7823
39+
40+
### Changed
41+
- **PrecompileId type**: Changed from `enum` to `union(enum)` to support Custom variant while maintaining backward compatibility with all existing precompile IDs
42+
43+
## [0.3.0] - 2025-12-01
2944

3045
### Added
3146
- **Cross-Platform Makefile**: Comprehensive build system with OS detection and automated dependency installation

src/precompile/main.zig

Lines changed: 94 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
const std = @import("std");
22
const primitives = @import("primitives");
33

4+
// Import precompile modules (needed for PrecompileId.precompile method)
5+
pub const identity = @import("identity.zig");
6+
pub const hash = @import("hash.zig");
7+
pub const secp256k1 = @import("secp256k1.zig");
8+
pub const secp256r1 = @import("secp256r1.zig");
9+
pub const modexp = @import("modexp.zig");
10+
pub const bn254 = @import("bn254.zig");
11+
pub const blake2 = @import("blake2.zig");
12+
pub const kzg_point_evaluation = @import("kzg_point_evaluation.zig");
13+
pub const bls12_381 = @import("bls12_381.zig");
14+
415
/// Precompile error type
516
pub const PrecompileError = error{
617
OutOfGas,
@@ -71,7 +82,7 @@ pub const PrecompileOutput = struct {
7182
pub const PrecompileFn = *const fn (input: []const u8, gas_limit: u64) PrecompileResult;
7283

7384
/// Precompile identifier
74-
pub const PrecompileId = enum {
85+
pub const PrecompileId = union(enum) {
7586
/// Elliptic curve digital signature algorithm (ECDSA) public key recovery function.
7687
EcRec,
7788
/// SHA2-256 hash function.
@@ -108,6 +119,88 @@ pub const PrecompileId = enum {
108119
Bls12MapFp2ToGp2,
109120
/// ECDSA signature verification over the secp256r1 elliptic curve.
110121
P256Verify,
122+
/// Custom precompile identifier.
123+
Custom: []const u8,
124+
125+
/// Create new custom precompile ID.
126+
pub fn custom(id: []const u8) PrecompileId {
127+
return PrecompileId{ .Custom = id };
128+
}
129+
130+
/// Returns the name of the precompile as defined in EIP-7910.
131+
pub fn name(self: PrecompileId) []const u8 {
132+
return switch (self) {
133+
.EcRec => "ECREC",
134+
.Sha256 => "SHA256",
135+
.Ripemd160 => "RIPEMD160",
136+
.Identity => "ID",
137+
.ModExp => "MODEXP",
138+
.Bn254Add => "BN254_ADD",
139+
.Bn254Mul => "BN254_MUL",
140+
.Bn254Pairing => "BN254_PAIRING",
141+
.Blake2F => "BLAKE2F",
142+
.KzgPointEvaluation => "KZG_POINT_EVALUATION",
143+
.Bls12G1Add => "BLS12_G1ADD",
144+
.Bls12G1Msm => "BLS12_G1MSM",
145+
.Bls12G2Add => "BLS12_G2ADD",
146+
.Bls12G2Msm => "BLS12_G2MSM",
147+
.Bls12Pairing => "BLS12_PAIRING_CHECK",
148+
.Bls12MapFpToGp1 => "BLS12_MAP_FP_TO_G1",
149+
.Bls12MapFp2ToGp2 => "BLS12_MAP_FP2_TO_G2",
150+
.P256Verify => "P256VERIFY",
151+
.Custom => |id| id,
152+
};
153+
}
154+
155+
/// Returns the precompile function for the given spec.
156+
///
157+
/// If case of Custom it will return null.
158+
///
159+
/// For case where precompile was still not introduced in the spec,
160+
/// it will return the fork closest to activation.
161+
pub fn precompile(self: PrecompileId, spec: PrecompileSpecId) ?Precompile {
162+
return switch (self) {
163+
.EcRec => secp256k1.ECRECOVER,
164+
.Sha256 => hash.SHA256,
165+
.Ripemd160 => hash.RIPEMD160,
166+
.Identity => identity.FUN,
167+
.ModExp => blk: {
168+
// ModExp changes gas calculation based on spec
169+
const mod_exp_precompile: Precompile = if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Berlin)) modexp.BYZANTIUM else if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Osaka)) modexp.BERLIN else modexp.OSAKA;
170+
break :blk mod_exp_precompile;
171+
},
172+
.Bn254Add => blk: {
173+
// BN254 add - gas cost changes in Istanbul
174+
const bn254_add_precompile: Precompile = if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Istanbul)) bn254.add.BYZANTIUM else bn254.add.ISTANBUL;
175+
break :blk bn254_add_precompile;
176+
},
177+
.Bn254Mul => blk: {
178+
// BN254 mul - gas cost changes in Istanbul
179+
const bn254_mul_precompile: Precompile = if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Istanbul)) bn254.mul.BYZANTIUM else bn254.mul.ISTANBUL;
180+
break :blk bn254_mul_precompile;
181+
},
182+
.Bn254Pairing => blk: {
183+
// BN254 pairing - gas cost changes in Istanbul
184+
const bn254_pair_precompile: Precompile = if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Istanbul)) bn254.pair.BYZANTIUM else bn254.pair.ISTANBUL;
185+
break :blk bn254_pair_precompile;
186+
},
187+
.Blake2F => blake2.FUN,
188+
.KzgPointEvaluation => kzg_point_evaluation.POINT_EVALUATION,
189+
.Bls12G1Add => bls12_381.g1_add.PRECOMPILE,
190+
.Bls12G1Msm => bls12_381.g1_msm.PRECOMPILE,
191+
.Bls12G2Add => bls12_381.g2_add.PRECOMPILE,
192+
.Bls12G2Msm => bls12_381.g2_msm.PRECOMPILE,
193+
.Bls12Pairing => bls12_381.pairing.PRECOMPILE,
194+
.Bls12MapFpToGp1 => bls12_381.map_fp_to_g1.PRECOMPILE,
195+
.Bls12MapFp2ToGp2 => bls12_381.map_fp2_to_g2.PRECOMPILE,
196+
.P256Verify => blk: {
197+
// P256 verify - gas cost changes in Osaka
198+
const p256_precompile: Precompile = if (@intFromEnum(spec) < @intFromEnum(PrecompileSpecId.Osaka)) secp256r1.P256VERIFY else secp256r1.P256VERIFY_OSAKA;
199+
break :blk p256_precompile;
200+
},
201+
.Custom => return null,
202+
};
203+
}
111204
};
112205

113206
/// Precompile specification ID
@@ -309,16 +402,6 @@ pub const Precompiles = struct {
309402
}
310403
};
311404

312-
// Import precompile modules
313-
pub const identity = @import("identity.zig");
314-
pub const hash = @import("hash.zig");
315-
pub const secp256k1 = @import("secp256k1.zig");
316-
pub const secp256r1 = @import("secp256r1.zig");
317-
pub const modexp = @import("modexp.zig");
318-
pub const bn254 = @import("bn254.zig");
319-
pub const blake2 = @import("blake2.zig");
320-
pub const kzg_point_evaluation = @import("kzg_point_evaluation.zig");
321-
pub const bls12_381 = @import("bls12_381.zig");
322405

323406
// Import test module
324407
pub const tests = @import("tests.zig");

src/precompile/modexp.zig

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,12 @@ fn berlinGasCalc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: primitive
113113
return 200 + @as(u64, @intCast(complexity * iteration_count / 3));
114114
}
115115

116-
/// Calculate gas cost for Osaka (EIP-7823)
116+
/// Calculate gas cost for Osaka (EIP-7823 and EIP-7883)
117+
/// Formula: max(500, complexity * iteration_count)
118+
/// where complexity is based on max(base_len, mod_len), not exp_len
117119
fn osakaGasCalc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: primitives.U256) u64 {
118-
const max_len = @max(@max(base_len, exp_len), mod_len);
120+
// Use max(base_len, mod_len) for complexity, not exp_len
121+
const max_len = @max(base_len, mod_len);
119122
const iteration_count = calculateIterationCount(exp_len, exp_highp, 16);
120123

121124
var complexity: u64 = 0;
@@ -126,7 +129,8 @@ fn osakaGasCalc(base_len: u64, exp_len: u64, mod_len: u64, exp_highp: primitives
126129
complexity = 2 * words * words;
127130
}
128131

129-
return 500 + @as(u64, @intCast(complexity * iteration_count));
132+
const gas = complexity * iteration_count;
133+
return @max(@as(u64, @intCast(500)), gas);
130134
}
131135

132136
/// Run modexp with specific gas calculation
@@ -155,8 +159,8 @@ fn runInner(
155159
const exp_len_u256 = extractU256(&exp_len_bytes);
156160
const mod_len_u256 = extractU256(&mod_len_bytes);
157161

158-
// Check EIP-7823 limits for Osaka
159-
const EIP7823_LIMIT: u64 = 32768;
162+
// Check EIP-7823 limits for Osaka (1024 bytes per parameter)
163+
const EIP7823_LIMIT: u64 = 1024;
160164
if (is_osaka) {
161165
if (base_len_u256 > EIP7823_LIMIT or exp_len_u256 > EIP7823_LIMIT or mod_len_u256 > EIP7823_LIMIT) {
162166
return main.PrecompileResult{ .err = main.PrecompileError.ModexpEip7823LimitSize };

src/version.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// Version information for ZEVM
2-
pub const VERSION = "0.3.0";
2+
pub const VERSION = "0.3.1";
33
pub const VERSION_MAJOR = 0;
44
pub const VERSION_MINOR = 3;
5-
pub const VERSION_PATCH = 0;
5+
pub const VERSION_PATCH = 1;
66
pub const VERSION_STRING = "ZEVM v" ++ VERSION;
7-
pub const RELEASE_DATE = "2025-12-20";
7+
pub const RELEASE_DATE = "2025-12-02";
88

99
// Build information
1010
pub const BUILD_DATE = @compileError("Build date should be set by build system");

0 commit comments

Comments
 (0)