From f95ad15eeaa0c010a6725b2981cd8f77a8c9058f Mon Sep 17 00:00:00 2001 From: lightsing Date: Thu, 19 Jun 2025 19:14:49 +0800 Subject: [PATCH 1/6] add openvm --- Cargo.lock | 491 ++++++++++++++++++++++++++++- Cargo.toml | 29 +- src/precompile/blake2.rs | 12 +- src/precompile/bn128.rs | 57 ---- src/precompile/bn128/arkworks.rs | 215 +++++++++++++ src/precompile/bn128/mod.rs | 505 ++++++++++++++++++++++++++++++ src/precompile/bn128/openvm.rs | 174 ++++++++++ src/precompile/bn128/substrate.rs | 168 ++++++++++ src/precompile/hash.rs | 42 +-- src/precompile/mod.rs | 16 +- src/precompile/modexp.rs | 9 +- 11 files changed, 1605 insertions(+), 113 deletions(-) delete mode 100644 src/precompile/bn128.rs create mode 100644 src/precompile/bn128/arkworks.rs create mode 100644 src/precompile/bn128/mod.rs create mode 100644 src/precompile/bn128/openvm.rs create mode 100644 src/precompile/bn128/substrate.rs diff --git a/Cargo.lock b/Cargo.lock index 1ef2f79..9a2540e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,6 +557,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.9.0" @@ -593,6 +604,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytemuck" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" + [[package]] name = "byteorder" version = "1.5.0" @@ -657,6 +674,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -681,6 +704,31 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "crunchy" version = "0.2.2" @@ -918,6 +966,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "byteorder", "ff_derive", "rand_core 0.6.4", @@ -1027,6 +1076,34 @@ dependencies = [ "subtle", ] +[[package]] +name = "halo2curves-axiom" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8309e4638b4f1bcf6613d72265a84074d26034c35edc5d605b5688e580b8b8" +dependencies = [ + "blake2b_simd", + "digest 0.10.7", + "ff", + "group", + "hex", + "lazy_static", + "num-bigint 0.4.6", + "num-traits", + "pairing", + "pasta_curves", + "paste", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "serde", + "serde_arrays", + "sha2 0.10.8", + "static_assertions", + "subtle", + "unroll", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -1034,10 +1111,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", + "equivalent", "foldhash", "serde", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1062,6 +1146,12 @@ dependencies = [ "arrayvec", ] +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hmac" version = "0.12.1" @@ -1129,6 +1219,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1185,6 +1284,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1250,6 +1352,15 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1289,6 +1400,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.5", ] [[package]] @@ -1320,6 +1432,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-modular" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-prime" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e238432a7881ec7164503ccc516c014bf009be7984cde1ba56837862543bdec3" +dependencies = [ + "bitvec", + "either", + "lru", + "num-bigint 0.4.6", + "num-integer", + "num-modular", + "num-traits", + "rand 0.8.5", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -1372,6 +1511,18 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "nums" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3c74f925fb8cfc49a8022f2afce48a0683b70f9e439885594e84c5edbf5b01" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "rand 0.8.5", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1384,6 +1535,185 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openvm" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "bytemuck", + "num-bigint 0.4.6", + "openvm-custom-insn", + "openvm-platform", + "openvm-rv32im-guest", + "serde", +] + +[[package]] +name = "openvm-algebra-complex-macros" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openvm-algebra-guest" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "halo2curves-axiom", + "num-bigint 0.4.6", + "once_cell", + "openvm-algebra-complex-macros", + "openvm-algebra-moduli-macros", + "openvm-custom-insn", + "openvm-rv32im-guest", + "serde-big-array", + "strum_macros", +] + +[[package]] +name = "openvm-algebra-moduli-macros" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "num-bigint 0.4.6", + "num-prime", + "openvm-macros-common", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openvm-custom-insn" +version = "0.1.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openvm-ecc-guest" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "ecdsa", + "elliptic-curve", + "group", + "halo2curves-axiom", + "once_cell", + "openvm", + "openvm-algebra-guest", + "openvm-custom-insn", + "openvm-ecc-sw-macros", + "openvm-rv32im-guest", + "serde", + "strum_macros", +] + +[[package]] +name = "openvm-ecc-sw-macros" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "openvm-macros-common", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openvm-macros-common" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "syn 2.0.100", +] + +[[package]] +name = "openvm-pairing" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "group", + "hex-literal", + "itertools 0.14.0", + "num-bigint 0.4.6", + "num-traits", + "openvm", + "openvm-algebra-complex-macros", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-custom-insn", + "openvm-ecc-guest", + "openvm-ecc-sw-macros", + "openvm-pairing-guest", + "openvm-platform", + "openvm-rv32im-guest", + "rand 0.8.5", + "serde", +] + +[[package]] +name = "openvm-pairing-guest" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "hex-literal", + "itertools 0.14.0", + "lazy_static", + "num-bigint 0.4.6", + "num-traits", + "openvm", + "openvm-algebra-guest", + "openvm-algebra-moduli-macros", + "openvm-custom-insn", + "openvm-ecc-guest", + "rand 0.8.5", + "serde", + "strum_macros", +] + +[[package]] +name = "openvm-platform" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "libm", + "openvm-custom-insn", + "openvm-rv32im-guest", +] + +[[package]] +name = "openvm-rv32im-guest" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "openvm-custom-insn", + "p3-field 0.1.0", + "strum_macros", +] + +[[package]] +name = "openvm-sha2" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "openvm-sha256-guest", + "sha2 0.10.8", +] + +[[package]] +name = "openvm-sha256-guest" +version = "1.2.1-rc.0" +source = "git+https://github.com/openvm-org/openvm.git?rev=9ef822c947f14eb436e293971c969cfcb688dbe7#9ef822c947f14eb436e293971c969cfcb688dbe7" +dependencies = [ + "openvm-platform", +] + [[package]] name = "p256" version = "0.13.2" @@ -1403,7 +1733,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "080896e9d09e9761982febafe3b3da5cbf320e32f0c89b6e2e01e875129f4c2d" dependencies = [ "num-bigint 0.4.6", - "p3-field", + "p3-field 0.2.0-succinct", "p3-mds", "p3-poseidon2", "p3-symmetric", @@ -1417,10 +1747,27 @@ version = "0.2.0-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "292e97d02d4c38d8b306c2b8c0428bf15f4d32a11a40bcf80018f675bf33267e" dependencies = [ - "p3-field", + "p3-field 0.2.0-succinct", "p3-matrix", - "p3-maybe-rayon", - "p3-util", + "p3-maybe-rayon 0.2.0", + "p3-util 0.2.0", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.1.0" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=539bbc84085efb609f4f62cb03cf49588388abdb#539bbc84085efb609f4f62cb03cf49588388abdb" +dependencies = [ + "itertools 0.14.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "nums", + "p3-maybe-rayon 0.1.0", + "p3-util 0.1.0", + "rand 0.8.5", + "serde", "tracing", ] @@ -1433,7 +1780,7 @@ dependencies = [ "itertools 0.12.1", "num-bigint 0.4.6", "num-traits", - "p3-util", + "p3-util 0.2.0", "rand 0.8.5", "serde", ] @@ -1445,14 +1792,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98bf2c7680b8e906a5e147fe4ceb05a11cc9fa35678aa724333bcb35c72483c1" dependencies = [ "itertools 0.12.1", - "p3-field", - "p3-maybe-rayon", - "p3-util", + "p3-field 0.2.0-succinct", + "p3-maybe-rayon 0.2.0", + "p3-util 0.2.0", "rand 0.8.5", "serde", "tracing", ] +[[package]] +name = "p3-maybe-rayon" +version = "0.1.0" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=539bbc84085efb609f4f62cb03cf49588388abdb#539bbc84085efb609f4f62cb03cf49588388abdb" + [[package]] name = "p3-maybe-rayon" version = "0.2.0" @@ -1467,10 +1819,10 @@ checksum = "706cea48976f54702dc68dffa512684c1304d1a3606cadea423cfe0b1ee25134" dependencies = [ "itertools 0.12.1", "p3-dft", - "p3-field", + "p3-field 0.2.0-succinct", "p3-matrix", "p3-symmetric", - "p3-util", + "p3-util 0.2.0", "rand 0.8.5", ] @@ -1481,7 +1833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ce5f5ec7f1ba3a233a671621029def7bd416e7c51218c9d1167d21602cf312" dependencies = [ "gcd", - "p3-field", + "p3-field 0.2.0-succinct", "p3-mds", "p3-symmetric", "rand 0.8.5", @@ -1495,7 +1847,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f29dc5bb6c99d3de75869d5c086874b64890280eeb7d3e068955f939e219253" dependencies = [ "itertools 0.12.1", - "p3-field", + "p3-field 0.2.0-succinct", + "serde", +] + +[[package]] +name = "p3-util" +version = "0.1.0" +source = "git+https://github.com/Plonky3/Plonky3.git?rev=539bbc84085efb609f4f62cb03cf49588388abdb#539bbc84085efb609f4f62cb03cf49588388abdb" +dependencies = [ "serde", ] @@ -1543,6 +1903,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -1784,6 +2159,26 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "regex-syntax" version = "0.8.5" @@ -1941,6 +2336,7 @@ dependencies = [ "ripemd", "secp256k1", "sha2 0.10.8", + "substrate-bn", ] [[package]] @@ -1957,13 +2353,22 @@ dependencies = [ name = "revm-scroll" version = "0.1.0" dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", "auto_impl", + "cfg-if", "enumn", "once_cell", + "openvm-ecc-guest", + "openvm-pairing", + "openvm-sha2", "revm", "revm-inspector", "revm-primitives", "serde", + "substrate-bn", ] [[package]] @@ -2082,6 +2487,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + [[package]] name = "rusty-fork" version = "0.3.0" @@ -2167,6 +2578,24 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_arrays" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -2279,7 +2708,7 @@ dependencies = [ "lazy_static", "num-bigint 0.4.6", "p3-baby-bear", - "p3-field", + "p3-field 0.2.0-succinct", "p3-poseidon2", "p3-symmetric", "serde", @@ -2323,6 +2752,32 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2528,6 +2983,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index d8da513..35b6f9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,29 @@ revm-primitives = { git = "https://github.com/scroll-tech/revm", branch = "feat/ revm-inspector = { git = "https://github.com/scroll-tech/revm", branch = "feat/reth-v74", default-features = false } # misc +cfg-if = "1.0" auto_impl = "1.2.0" enumn = { version = "0.1" } once_cell = { version = "1.19", default-features = false, features = ["alloc"] } serde = { version = "1.0", features = ["derive"], optional = true, default-features = false } +# Optionally use substrate implementation for eip1962 +bn = { package = "substrate-bn", version = "0.6", default-features = false, optional = true } + +# Use arkworks implementation for eip1962 +ark-bn254 = { version = "0.5", default-features = false, optional = true } +ark-ec = { version = "0.5", default-features = false, optional = true } +ark-ff = { version = "0.5", default-features = false, optional = true } +ark-serialize = { version = "0.5", default-features = false, optional = true } + + +# openvm +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } +openvm-sha2 = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } +openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } + [features] -default = ["std", "c-kzg", "secp256k1", "portable", "blst"] +default = ["std", "c-kzg", "secp256k1", "portable", "blst", "arkworks"] std = ["serde?/std", "revm/std"] hashbrown = ["revm/hashbrown"] serde = ["dep:serde", "revm/serde"] @@ -29,3 +45,14 @@ c-kzg = ["revm/c-kzg"] # `kzg-rs` is not audited but useful for `no_std` environment, use it with causing and default to `c-kzg` if possible. kzg-rs = ["revm/kzg-rs"] blst = ["revm/blst"] + +# bn128 related +bn = ["revm/bn", "dep:bn"] +arkworks = ["dep:ark-bn254", "dep:ark-ec", "dep:ark-ff", "dep:ark-serialize", "ark-bn254/curve", "ark-ff/asm"] + +openvm = [ + "dep:openvm-ecc-guest", + "dep:openvm-sha2", + "dep:openvm-pairing", + "openvm-pairing/bn254", +] diff --git a/src/precompile/blake2.rs b/src/precompile/blake2.rs index 19f9022..f890e11 100644 --- a/src/precompile/blake2.rs +++ b/src/precompile/blake2.rs @@ -1,18 +1,12 @@ use super::precompile_not_implemented; -use revm::{ - precompile::{u64_to_address, PrecompileWithAddress}, - primitives::Address, -}; +use revm::{precompile::PrecompileWithAddress, primitives::Address}; // CONSTANTS // ================================================================================================ -/// The BLAKE2 precompile index. -const BLAKE2_PRECOMPILE_INDEX: u64 = 9; - /// The BLAKE2 precompile address. -const BLAKE2_PRECOMPILE_ADDRESS: Address = u64_to_address(BLAKE2_PRECOMPILE_INDEX); +const ADDRESS: Address = revm::precompile::blake2::FUN.0; // BLAKE2 PRECOMPILE // ================================================================================================ @@ -21,4 +15,4 @@ const BLAKE2_PRECOMPILE_ADDRESS: Address = u64_to_address(BLAKE2_PRECOMPILE_INDE /// /// This precompile is not implemented and will return `PrecompileError::Other("Precompile not /// implemented".into())`. -pub const SHANGHAI: PrecompileWithAddress = precompile_not_implemented(BLAKE2_PRECOMPILE_ADDRESS); +pub const SHANGHAI: PrecompileWithAddress = precompile_not_implemented(ADDRESS); diff --git a/src/precompile/bn128.rs b/src/precompile/bn128.rs deleted file mode 100644 index 4e7dd1c..0000000 --- a/src/precompile/bn128.rs +++ /dev/null @@ -1,57 +0,0 @@ -use revm::{ - precompile::{ - bn128::{ - pair::{ISTANBUL_PAIR_BASE, ISTANBUL_PAIR_PER_POINT}, - run_pair, - }, - u64_to_address, PrecompileError, PrecompileResult, PrecompileWithAddress, - }, - primitives::Address, -}; - -pub mod pair { - use super::*; - - // CONSTANTS - // -------------------------------------------------------------------------------------------- - - /// The BN128 pairing precompile index. - const BN128_PAIRING_PRECOMPILE_INDEX: u64 = 8; - - /// The BN128 pairing precompile address. - pub const BN128_PAIRING_PRECOMPILE_ADDRESS: Address = - u64_to_address(BN128_PAIRING_PRECOMPILE_INDEX); - - /// The number of pairing inputs per pairing operation. If the inputs provided to the precompile - /// call are < 4, we append (G1::infinity, G2::generator) until we have the required no. of - /// inputs. - const N_PAIRING_PER_OP: usize = 4; - - /// The number of bytes taken to represent a pair (G1, G2). - const N_BYTES_PER_PAIR: usize = 192; - - // BN128 PAIRING PRECOMPILE - // -------------------------------------------------------------------------------------------- - - /// The BN128 PAIRING precompile with address. - pub const BERNOULLI: PrecompileWithAddress = - PrecompileWithAddress(BN128_PAIRING_PRECOMPILE_ADDRESS, bernoulli_run); - - /// The bernoulli BN128 PAIRING precompile implementation. - /// - /// # Errors - /// - `PrecompileError::Other("BN128PairingInputOverflow: input overflow".into())` if the input - /// length is greater than 768 bytes. - fn bernoulli_run(input: &[u8], gas_limit: u64) -> PrecompileResult { - if input.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR { - return Err(PrecompileError::Other("BN128PairingInputOverflow: input overflow".into())); - } - run_pair(input, ISTANBUL_PAIR_PER_POINT, ISTANBUL_PAIR_BASE, gas_limit) - } - - /// The BN128 PAIRING precompile with address. - pub const FEYNMAN: PrecompileWithAddress = - PrecompileWithAddress(BN128_PAIRING_PRECOMPILE_ADDRESS, |input, gas_limit| { - run_pair(input, ISTANBUL_PAIR_PER_POINT, ISTANBUL_PAIR_BASE, gas_limit) - }); -} diff --git a/src/precompile/bn128/arkworks.rs b/src/precompile/bn128/arkworks.rs new file mode 100644 index 0000000..c833681 --- /dev/null +++ b/src/precompile/bn128/arkworks.rs @@ -0,0 +1,215 @@ +// Copied from https://github.com/bluealloy/revm/blob/v75/crates/precompile/src/bn128/arkworks.rs under MIT License +use super::{PrecompileError, FQ2_LEN, FQ_LEN, G1_LEN, SCALAR_LEN}; +use std::vec::Vec; + +use ark_bn254::{Bn254, Fq, Fq2, Fr, G1Affine, G1Projective, G2Affine}; +use ark_ec::{pairing::Pairing, AffineRepr, CurveGroup}; +use ark_ff::{One, PrimeField, Zero}; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; + +/// Reads a single `Fq` field element from the input slice. +/// +/// Takes a byte slice and attempts to interpret the first 32 bytes as an +/// elliptic curve field element. Returns an error if the bytes do not form +/// a valid field element. +/// +/// # Panics +/// +/// Panics if the input is not at least 32 bytes long. +#[inline] +fn read_fq(input_be: &[u8]) -> Result { + assert_eq!(input_be.len(), FQ_LEN, "input must be {FQ_LEN} bytes"); + + let mut input_le = [0u8; FQ_LEN]; + input_le.copy_from_slice(input_be); + + // Reverse in-place to convert from big-endian to little-endian. + input_le.reverse(); + + Fq::deserialize_uncompressed(&input_le[..]) + .map_err(|_| PrecompileError::Bn128FieldPointNotAMember) +} +/// Reads a Fq2 (quadratic extension field element) from the input slice. +/// +/// Parses two consecutive Fq field elements as the real and imaginary parts +/// of an Fq2 element. +/// The second component is parsed before the first, ie if a we represent an +/// element in Fq2 as (x,y) -- `y` is parsed before `x` +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +fn read_fq2(input: &[u8]) -> Result { + let y = read_fq(&input[..FQ_LEN])?; + let x = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + + Ok(Fq2::new(x, y)) +} + +/// Creates a new `G1` point from the given `x` and `y` coordinates. +/// +/// Constructs a point on the G1 curve from its affine coordinates. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically because `AffineG1` is not capable of +/// representing such a point. +/// In particular, when we convert from `AffineG1` to `G1`, the point +/// will be (0,0,1) instead of (0,1,0) +#[inline] +fn new_g1_point(px: Fq, py: Fq) -> Result { + if px.is_zero() && py.is_zero() { + Ok(G1Affine::zero()) + } else { + // We cannot use `G1Affine::new` because that triggers an assert if the point is not on the + // curve. + let point = G1Affine::new_unchecked(px, py); + if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(PrecompileError::Bn128AffineGFailedToCreate); + } + Ok(point) + } +} + +/// Creates a new `G2` point from the given Fq2 coordinates. +/// +/// G2 points in BN128 are defined over a quadratic extension field Fq2. +/// This function takes two Fq2 elements representing the x and y coordinates +/// and creates a G2 point. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically because `AffineG2` is not capable of +/// representing such a point. +/// In particular, when we convert from `AffineG2` to `G2`, the point +/// will be (0,0,1) instead of (0,1,0) +#[inline] +fn new_g2_point(x: Fq2, y: Fq2) -> Result { + let point = if x.is_zero() && y.is_zero() { + G2Affine::zero() + } else { + // We cannot use `G1Affine::new` because that triggers an assert if the point is not on the + // curve. + let point = G2Affine::new_unchecked(x, y); + if !point.is_on_curve() || !point.is_in_correct_subgroup_assuming_on_curve() { + return Err(PrecompileError::Bn128AffineGFailedToCreate); + } + point + }; + + Ok(point) +} + +/// Reads a G1 point from the input slice. +/// +/// Parses a G1 point from a byte slice by reading two consecutive field elements +/// representing the x and y coordinates. +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +pub(super) fn read_g1_point(input: &[u8]) -> Result { + let px = read_fq(&input[0..FQ_LEN])?; + let py = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + new_g1_point(px, py) +} + +/// Encodes a G1 point into a byte array. +/// +/// Converts a G1 point in Jacobian coordinates to affine coordinates and +/// serializes the x and y coordinates as big-endian byte arrays. +/// +/// Note: If the point is the point at infinity, this function returns +/// all zeroes. +#[inline] +pub(super) fn encode_g1_point(point: G1Affine) -> [u8; G1_LEN] { + let mut output = [0u8; G1_LEN]; + let Some((x, y)) = point.xy() else { + return output; + }; + + let mut x_bytes = [0u8; FQ_LEN]; + x.serialize_uncompressed(&mut x_bytes[..]).expect("Failed to serialize x coordinate"); + + let mut y_bytes = [0u8; FQ_LEN]; + y.serialize_uncompressed(&mut y_bytes[..]).expect("Failed to serialize x coordinate"); + + // Convert to big endian by reversing the bytes. + x_bytes.reverse(); + y_bytes.reverse(); + + // Place x in the first half, y in the second half. + output[0..FQ_LEN].copy_from_slice(&x_bytes); + output[FQ_LEN..(FQ_LEN * 2)].copy_from_slice(&y_bytes); + + output +} + +/// Reads a G2 point from the input slice. +/// +/// Parses a G2 point from a byte slice by reading four consecutive Fq field elements +/// representing the two Fq2 coordinates (x and y) of the G2 point. +/// +/// # Panics +/// +/// Panics if the input is not at least 128 bytes long. +#[inline] +pub(super) fn read_g2_point(input: &[u8]) -> Result { + let ba = read_fq2(&input[0..FQ2_LEN])?; + let bb = read_fq2(&input[FQ2_LEN..2 * FQ2_LEN])?; + new_g2_point(ba, bb) +} + +/// Reads a scalar from the input slice +/// +/// Note: The scalar does not need to be canonical. +/// +/// # Panics +/// +/// If `input.len()` is not equal to [`SCALAR_LEN`]. +#[inline] +pub(super) fn read_scalar(input: &[u8]) -> Fr { + assert_eq!( + input.len(), + SCALAR_LEN, + "unexpected scalar length. got {}, expected {SCALAR_LEN}", + input.len() + ); + Fr::from_be_bytes_mod_order(input) +} + +/// Performs point addition on two G1 points. +#[inline] +pub(super) fn g1_point_add(p1: G1Affine, p2: G1Affine) -> G1Affine { + let p1_jacobian: G1Projective = p1.into(); + + let p3 = p1_jacobian + p2; + + p3.into_affine() +} + +/// Performs a G1 scalar multiplication. +#[inline] +pub(super) fn g1_point_mul(p: G1Affine, fr: Fr) -> G1Affine { + let big_int = fr.into_bigint(); + let result = p.mul_bigint(big_int); + + result.into_affine() +} + +/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and +/// returns true if the result is equal to the identity element. +/// +/// Note: If the input is empty, this function returns true. +/// This is different to EIP2537 which disallows the empty input. +#[inline] +pub(super) fn pairing_check(pairs: &[(G1Affine, G2Affine)]) -> bool { + if pairs.is_empty() { + return true; + } + + let (g1_points, g2_points): (Vec, Vec) = pairs.iter().copied().unzip(); + + let pairing_result = Bn254::multi_pairing(&g1_points, &g2_points); + pairing_result.0.is_one() +} diff --git a/src/precompile/bn128/mod.rs b/src/precompile/bn128/mod.rs new file mode 100644 index 0000000..4ac7840 --- /dev/null +++ b/src/precompile/bn128/mod.rs @@ -0,0 +1,505 @@ +use revm::precompile::{ + bn128::{ADD_INPUT_LEN, MUL_INPUT_LEN, PAIR_ELEMENT_LEN}, + utilities::{bool_to_bytes32, right_pad}, + PrecompileError, PrecompileOutput, PrecompileResult, PrecompileWithAddress, +}; +use std::vec::Vec; + +cfg_if::cfg_if! { + if #[cfg(feature = "openvm")] { + mod openvm; + use openvm::{ + encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, + read_scalar, + }; + } else if #[cfg(feature = "bn")]{ + mod substrate; + use substrate::{ + encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, + read_scalar, + }; + } else if #[cfg(feature = "arkworks")] { + mod arkworks; + use arkworks::{ + encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, + read_scalar, + }; + } else { + compile_error!("At least one of the features 'bn', 'arkworks', or 'openvm' must be enabled for bn128 precompiles."); + } +} + +/// Bn128 add precompile +pub mod add { + use super::*; + + pub use revm::precompile::bn128::add::{ADDRESS, ISTANBUL_ADD_GAS_COST}; + + /// Bn128 add precompile with ISTANBUL gas rules + pub const ISTANBUL: PrecompileWithAddress = + PrecompileWithAddress(ADDRESS, |input, gas_limit| { + run_add(input, ISTANBUL_ADD_GAS_COST, gas_limit) + }); +} + +/// Bn128 mul precompile +pub mod mul { + use super::*; + + pub use revm::precompile::bn128::mul::{ADDRESS, ISTANBUL_MUL_GAS_COST}; + + /// Bn128 mul precompile with ISTANBUL gas rules + pub const ISTANBUL: PrecompileWithAddress = + PrecompileWithAddress(ADDRESS, |input, gas_limit| { + run_mul(input, ISTANBUL_MUL_GAS_COST, gas_limit) + }); +} + +pub mod pair { + use super::*; + + pub use revm::precompile::bn128::pair::{ADDRESS, ISTANBUL_PAIR_BASE, ISTANBUL_PAIR_PER_POINT}; + + // CONSTANTS + // -------------------------------------------------------------------------------------------- + + /// The number of pairing inputs per pairing operation. If the inputs provided to the precompile + /// call are < 4, we append (G1::infinity, G2::generator) until we have the required no. of + /// inputs. + const N_PAIRING_PER_OP: usize = 4; + + /// The number of bytes taken to represent a pair (G1, G2). + const N_BYTES_PER_PAIR: usize = 192; + + // BN128 PAIRING PRECOMPILE + // -------------------------------------------------------------------------------------------- + + /// The BN128 PAIRING precompile with address. + pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress(ADDRESS, bernoulli_run); + + /// The bernoulli BN128 PAIRING precompile implementation. + /// + /// # Errors + /// - `PrecompileError::Other("BN128PairingInputOverflow: input overflow".into())` if the input + /// length is greater than 768 bytes. + fn bernoulli_run(input: &[u8], gas_limit: u64) -> PrecompileResult { + if input.len() > N_PAIRING_PER_OP * N_BYTES_PER_PAIR { + return Err(PrecompileError::Other("BN128PairingInputOverflow: input overflow".into())); + } + run_pair(input, ISTANBUL_PAIR_PER_POINT, ISTANBUL_PAIR_BASE, gas_limit) + } + + /// The BN128 PAIRING precompile with address. + pub const FEYNMAN: PrecompileWithAddress = + PrecompileWithAddress(ADDRESS, |input, gas_limit| { + run_pair(input, ISTANBUL_PAIR_PER_POINT, ISTANBUL_PAIR_BASE, gas_limit) + }); +} + +// Copied from https://github.com/bluealloy/revm/blob/v75/crates/precompile/src/bn128.rs Under MIT License + +/// FQ_LEN specifies the number of bytes needed to represent an +/// Fq element. This is an element in the base field of BN254. +/// +/// Note: The base field is used to define G1 and G2 elements. +const FQ_LEN: usize = 32; + +/// SCALAR_LEN specifies the number of bytes needed to represent an Fr element. +/// This is an element in the scalar field of BN254. +const SCALAR_LEN: usize = 32; + +/// FQ2_LEN specifies the number of bytes needed to represent an +/// Fq^2 element. +/// +/// Note: This is the quadratic extension of Fq, and by definition +/// means we need 2 Fq elements. +const FQ2_LEN: usize = 2 * FQ_LEN; + +/// G1_LEN specifies the number of bytes needed to represent a G1 element. +/// +/// Note: A G1 element contains 2 Fq elements. +const G1_LEN: usize = 2 * FQ_LEN; +/// G2_LEN specifies the number of bytes needed to represent a G2 element. +/// +/// Note: A G2 element contains 2 Fq^2 elements. +const G2_LEN: usize = 2 * FQ2_LEN; + +/// Run the Bn128 add precompile +pub fn run_add(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { + if gas_cost > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + let input = right_pad::(input); + + let p1 = read_g1_point(&input[..G1_LEN])?; + let p2 = read_g1_point(&input[G1_LEN..])?; + let result = g1_point_add(p1, p2); + + let output = encode_g1_point(result); + + Ok(PrecompileOutput::new(gas_cost, output.into())) +} + +/// Run the Bn128 mul precompile +pub fn run_mul(input: &[u8], gas_cost: u64, gas_limit: u64) -> PrecompileResult { + if gas_cost > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + let input = right_pad::(input); + + let p = read_g1_point(&input[..G1_LEN])?; + + let scalar = read_scalar(&input[G1_LEN..G1_LEN + SCALAR_LEN]); + let result = g1_point_mul(p, scalar); + + let output = encode_g1_point(result); + + Ok(PrecompileOutput::new(gas_cost, output.into())) +} + +/// Run the Bn128 pair precompile +pub fn run_pair( + input: &[u8], + pair_per_point_cost: u64, + pair_base_cost: u64, + gas_limit: u64, +) -> PrecompileResult { + let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * pair_per_point_cost + pair_base_cost; + if gas_used > gas_limit { + return Err(PrecompileError::OutOfGas); + } + + if input.len() % PAIR_ELEMENT_LEN != 0 { + return Err(PrecompileError::Bn128PairLength); + } + + let elements = input.len() / PAIR_ELEMENT_LEN; + + let mut points = Vec::with_capacity(elements); + + for idx in 0..elements { + // Offset to the start of the pairing element at index `idx` in the byte slice + let start = idx * PAIR_ELEMENT_LEN; + let g1_start = start; + // Offset to the start of the G2 element in the pairing element + // This is where G1 ends. + let g2_start = start + G1_LEN; + + let encoded_g1_element = &input[g1_start..g2_start]; + let encoded_g2_element = &input[g2_start..g2_start + G2_LEN]; + + // If either the G1 or G2 element is the encoded representation + // of the point at infinity, then these two points are no-ops + // in the pairing computation. + // + // Note: we do not skip the validation of these two elements even if + // one of them is the point at infinity because we could have G1 be + // the point at infinity and G2 be an invalid element or vice versa. + // In that case, the precompile should error because one of the elements + // was invalid. + let g1_is_zero = encoded_g1_element.iter().all(|i| *i == 0); + let g2_is_zero = encoded_g2_element.iter().all(|i| *i == 0); + + // Get G1 and G2 points from the input + let a = read_g1_point(encoded_g1_element)?; + let b = read_g2_point(encoded_g2_element)?; + + if !g1_is_zero && !g2_is_zero { + points.push((a, b)); + } + } + + let success = pairing_check(&points); + + Ok(PrecompileOutput::new(gas_used, bool_to_bytes32(success))) +} + +#[cfg(test)] +mod tests { + use revm::precompile::{ + bn128::{ + add::BYZANTIUM_ADD_GAS_COST, + mul::BYZANTIUM_MUL_GAS_COST, + pair::{BYZANTIUM_PAIR_BASE, BYZANTIUM_PAIR_PER_POINT}, + }, + PrecompileError, + }; + use revm_primitives::hex; + + use super::*; + + #[test] + fn test_alt_bn128_add() { + let input = hex::decode( + "\ + 18b18acfb4c2c30276db5411368e7185b311dd124691610c5d3b74034e093dc9\ + 063c909c4720840cb5134cb9f59fa749755796819658d32efc0d288198f37266\ + 07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed\ + 06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", + ) + .unwrap(); + let expected = hex::decode( + "\ + 2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703\ + 301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915", + ) + .unwrap(); + + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Zero sum test + let input = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + let expected = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Out of gas test + let input = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499); + + assert!(matches!(res, Err(PrecompileError::OutOfGas))); + + // No input test + let input = [0u8; 0]; + let expected = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Point not on curve fail + let input = hex::decode( + "\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111", + ) + .unwrap(); + + let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500); + assert!(matches!( + res, + Err(PrecompileError::Bn128AffineGFailedToCreate) + )); + } + + #[test] + fn test_alt_bn128_mul() { + let input = hex::decode( + "\ + 2bd3e6d0f3b142924f5ca7b49ce5b9d54c4703d7ae5648e61d02268b1a0a9fb7\ + 21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204\ + 00000000000000000000000000000000000000000000000011138ce750fa15c2", + ) + .unwrap(); + let expected = hex::decode( + "\ + 070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c\ + 031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc", + ) + .unwrap(); + + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Out of gas test + let input = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0200000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999); + assert!(matches!(res, Err(PrecompileError::OutOfGas))); + + // Zero multiplication test + let input = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0200000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + let expected = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); + + // No input test + let input = [0u8; 0]; + let expected = hex::decode( + "\ + 0000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); + assert_eq!(outcome.bytes, expected); + + // Point not on curve fail + let input = hex::decode( + "\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 0f00000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000); + assert!(matches!( + res, + Err(PrecompileError::Bn128AffineGFailedToCreate) + )); + } + + #[test] + fn test_alt_bn128_pair() { + let input = hex::decode( + "\ + 1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\ + 3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\ + 209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\ + 04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\ + 2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\ + 120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\ + 111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\ + 2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\ + 198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\ + 1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\ + 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ + 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ) + .unwrap(); + let expected = + hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + + let outcome = run_pair( + &input, + BYZANTIUM_PAIR_PER_POINT, + BYZANTIUM_PAIR_BASE, + 260_000, + ) + .unwrap(); + assert_eq!(outcome.bytes, expected); + + // Out of gas test + let input = hex::decode( + "\ + 1c76476f4def4bb94541d57ebba1193381ffa7aa76ada664dd31c16024c43f59\ + 3034dd2920f673e204fee2811c678745fc819b55d3e9d294e45c9b03a76aef41\ + 209dd15ebff5d46c4bd888e51a93cf99a7329636c63514396b4a452003a35bf7\ + 04bf11ca01483bfa8b34b43561848d28905960114c8ac04049af4b6315a41678\ + 2bb8324af6cfc93537a2ad1a445cfd0ca2a71acd7ac41fadbf933c2a51be344d\ + 120a2a4cf30c1bf9845f20c6fe39e07ea2cce61f0c9bb048165fe5e4de877550\ + 111e129f1cf1097710d41c4ac70fcdfa5ba2023c6ff1cbeac322de49d1b6df7c\ + 2032c61a830e3c17286de9462bf242fca2883585b93870a73853face6a6bf411\ + 198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2\ + 1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed\ + 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ + 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + ) + .unwrap(); + + let res = run_pair( + &input, + BYZANTIUM_PAIR_PER_POINT, + BYZANTIUM_PAIR_BASE, + 259_999, + ); + assert!(matches!(res, Err(PrecompileError::OutOfGas))); + + // No input test + let input = [0u8; 0]; + let expected = + hex::decode("0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + + let outcome = run_pair( + &input, + BYZANTIUM_PAIR_PER_POINT, + BYZANTIUM_PAIR_BASE, + 260_000, + ) + .unwrap(); + assert_eq!(outcome.bytes, expected); + + // Point not on curve fail + let input = hex::decode( + "\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111", + ) + .unwrap(); + + let res = run_pair( + &input, + BYZANTIUM_PAIR_PER_POINT, + BYZANTIUM_PAIR_BASE, + 260_000, + ); + assert!(matches!( + res, + Err(PrecompileError::Bn128AffineGFailedToCreate) + )); + + // Invalid input length + let input = hex::decode( + "\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 1111111111111111111111111111111111111111111111111111111111111111\ + 111111111111111111111111111111\ + ", + ) + .unwrap(); + + let res = run_pair( + &input, + BYZANTIUM_PAIR_PER_POINT, + BYZANTIUM_PAIR_BASE, + 260_000, + ); + assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); + } +} diff --git a/src/precompile/bn128/openvm.rs b/src/precompile/bn128/openvm.rs new file mode 100644 index 0000000..5e19fdf --- /dev/null +++ b/src/precompile/bn128/openvm.rs @@ -0,0 +1,174 @@ +// Copied from https://github.com/axiom-crypto/revm/blob/v75-openvm/crates/precompile/src/bn128.rs under MIT License +use revm::precompile::PrecompileError; +use std::vec::Vec; + +use openvm_ecc_guest::{ + algebra::IntMod, + weierstrass::{IntrinsicCurve, WeierstrassPoint}, + AffinePoint, +}; +use openvm_pairing::{ + bn254::{Bn254, Fp, Fp2, G1Affine, G2Affine, Scalar}, + PairingCheck, +}; + +/// FQ_LEN specifies the number of bytes needed to represent an +/// Fq element. This is an element in the base field of BN254. +/// +/// Note: The base field is used to define G1 and G2 elements. +const FQ_LEN: usize = 32; + +/// SCALAR_LEN specifies the number of bytes needed to represent an Fr element. +/// This is an element in the scalar field of BN254. +const SCALAR_LEN: usize = 32; + +/// FQ2_LEN specifies the number of bytes needed to represent an +/// Fq^2 element. +/// +/// Note: This is the quadratic extension of Fq, and by definition +/// means we need 2 Fq elements. +const FQ2_LEN: usize = 2 * FQ_LEN; + +/// G1_LEN specifies the number of bytes needed to represent a G1 element. +/// +/// Note: A G1 element contains 2 Fq elements. +const G1_LEN: usize = 2 * FQ_LEN; + +#[inline] +fn read_fq(input: &[u8]) -> Result { + if input.len() < FQ_LEN { + Err(PrecompileError::Bn128FieldPointNotAMember) + } else { + Fp::from_be_bytes(&input[..32]).ok_or(PrecompileError::Bn128FieldPointNotAMember) + } +} + +/// Reads a Fq2 (quadratic extension field element) from the input slice. +/// +/// Parses two consecutive Fq field elements as the real and imaginary parts +/// of an Fq2 element. +/// The second component is parsed before the first, ie if a we represent an +/// element in Fq2 as (x,y) -- `y` is parsed before `x` +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +fn read_fq2(input: &[u8]) -> Result { + let y = read_fq(&input[..FQ_LEN])?; + let x = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + Ok(Fp2::new(x, y)) +} + +#[inline] +fn new_g1_affine_point(px: Fp, py: Fp) -> Result { + G1Affine::from_xy(px, py).ok_or(PrecompileError::Bn128AffineGFailedToCreate) +} + +/// Reads a G1 point from the input slice. +/// +/// Parses a G1 point from a byte slice by reading two consecutive field elements +/// representing the x and y coordinates. +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +pub(super) fn read_g1_point(input: &[u8]) -> Result { + let px = read_fq(&input[0..FQ_LEN])?; + let py = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + new_g1_affine_point(px, py) +} + +/// Encodes a G1 point into a byte array. +/// +/// Converts a G1 point in Jacobian coordinates to affine coordinates and +/// serializes the x and y coordinates as big-endian byte arrays. +/// +/// Note: If the point is the point at infinity, this function returns +/// all zeroes. +#[inline] +pub(super) fn encode_g1_point(point: G1Affine) -> [u8; G1_LEN] { + let mut output = [0u8; G1_LEN]; + + // manually reverse to avoid allocation + let x_bytes: &[u8] = point.x().as_le_bytes(); + let y_bytes: &[u8] = point.y().as_le_bytes(); + for i in 0..FQ_LEN { + output[i] = x_bytes[FQ_LEN - 1 - i]; + output[i + FQ_LEN] = y_bytes[FQ_LEN - 1 - i]; + } + output +} + +/// Reads a G2 point from the input slice. +/// +/// Parses a G2 point from a byte slice by reading four consecutive Fq field elements +/// representing the two Fq2 coordinates (x and y) of the G2 point. +/// +/// # Panics +/// +/// Panics if the input is not at least 128 bytes long. +#[inline] +pub(super) fn read_g2_point(input: &[u8]) -> Result { + let ba = read_fq2(&input[0..FQ2_LEN])?; + let bb = read_fq2(&input[FQ2_LEN..2 * FQ2_LEN])?; + + G2Affine::from_xy(ba, bb).ok_or(PrecompileError::Bn128AffineGFailedToCreate) +} + +/// Reads a scalar from the input slice +/// +/// Note: The scalar does not need to be canonical. +/// +/// # Panics +/// +/// If `input.len()` is not equal to [`SCALAR_LEN`]. +#[inline] +pub(super) fn read_scalar(input: &[u8]) -> Scalar { + assert_eq!( + input.len(), + SCALAR_LEN, + "unexpected scalar length. got {}, expected {SCALAR_LEN}", + input.len() + ); + Scalar::from_be_bytes_unchecked(input) +} + +/// Performs point addition on two G1 points. +#[inline] +pub(super) fn g1_point_add(p1: G1Affine, p2: G1Affine) -> G1Affine { + p1 + p2 +} + +/// Performs a G1 scalar multiplication. +#[inline] +pub(super) fn g1_point_mul(p: G1Affine, fr: Scalar) -> G1Affine { + Bn254::msm(&[fr], &[p]) +} + +/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and +/// returns true if the result is equal to the identity element. +/// +/// Note: If the input is empty, this function returns true. +/// This is different to EIP2537 which disallows the empty input. +#[inline] +pub(super) fn pairing_check(pairs: &[(G1Affine, G2Affine)]) -> bool { + if pairs.is_empty() { + return true; + } + let (g1_points, g2_points): (Vec<_>, Vec<_>) = pairs + .iter() + .cloned() + .map(|(g1, g2)| { + let (g1_x, g1_y) = g1.into_coords(); + let g1 = AffinePoint::new(g1_x, g1_y); + + let (g2_x, g2_y) = g2.into_coords(); + let g2 = AffinePoint::new(g2_x, g2_y); + (g1, g2) + }) + .unzip(); + + Bn254::pairing_check(&g1_points, &g2_points).is_ok() +} diff --git a/src/precompile/bn128/substrate.rs b/src/precompile/bn128/substrate.rs new file mode 100644 index 0000000..0395a17 --- /dev/null +++ b/src/precompile/bn128/substrate.rs @@ -0,0 +1,168 @@ +// Copied from https://github.com/bluealloy/revm/blob/v75/crates/precompile/src/bn128/substrate.rs under MIT License +use super::{PrecompileError, FQ2_LEN, FQ_LEN, G1_LEN, SCALAR_LEN}; +use bn::{AffineG1, AffineG2, Fq, Fq2, Group, Gt, G1, G2}; + +/// Reads a single `Fq` field element from the input slice. +/// +/// Takes a byte slice and attempts to interpret the first 32 bytes as an +/// elliptic curve field element. Returns an error if the bytes do not form +/// a valid field element. +/// +/// # Panics +/// +/// Panics if the input is not at least 32 bytes long. +#[inline] +fn read_fq(input: &[u8]) -> Result { + Fq::from_slice(&input[..FQ_LEN]).map_err(|_| PrecompileError::Bn128FieldPointNotAMember) +} +/// Reads a Fq2 (quadratic extension field element) from the input slice. +/// +/// Parses two consecutive Fq field elements as the real and imaginary parts +/// of an Fq2 element. +/// The second component is parsed before the first, ie if a we represent an +/// element in Fq2 as (x,y) -- `y` is parsed before `x` +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +fn read_fq2(input: &[u8]) -> Result { + let y = read_fq(&input[..FQ_LEN])?; + let x = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + Ok(Fq2::new(x, y)) +} + +/// Creates a new `G1` point from the given `x` and `y` coordinates. +/// +/// Constructs a point on the G1 curve from its affine coordinates. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically because `AffineG1` is not capable of +/// representing such a point. +/// In particular, when we convert from `AffineG1` to `G1`, the point +/// will be (0,0,1) instead of (0,1,0) +#[inline] +fn new_g1_point(px: Fq, py: Fq) -> Result { + if px == Fq::zero() && py == Fq::zero() { + Ok(G1::zero()) + } else { + AffineG1::new(px, py) + .map(Into::into) + .map_err(|_| PrecompileError::Bn128AffineGFailedToCreate) + } +} + +/// Creates a new `G2` point from the given Fq2 coordinates. +/// +/// G2 points in BN128 are defined over a quadratic extension field Fq2. +/// This function takes two Fq2 elements representing the x and y coordinates +/// and creates a G2 point. +/// +/// Note: The point at infinity which is represented as (0,0) is +/// handled specifically because `AffineG2` is not capable of +/// representing such a point. +/// In particular, when we convert from `AffineG2` to `G2`, the point +/// will be (0,0,1) instead of (0,1,0) +#[inline] +fn new_g2_point(x: Fq2, y: Fq2) -> Result { + let point = if x.is_zero() && y.is_zero() { + G2::zero() + } else { + G2::from(AffineG2::new(x, y).map_err(|_| PrecompileError::Bn128AffineGFailedToCreate)?) + }; + + Ok(point) +} + +/// Reads a G1 point from the input slice. +/// +/// Parses a G1 point from a byte slice by reading two consecutive field elements +/// representing the x and y coordinates. +/// +/// # Panics +/// +/// Panics if the input is not at least 64 bytes long. +#[inline] +pub(super) fn read_g1_point(input: &[u8]) -> Result { + let px = read_fq(&input[0..FQ_LEN])?; + let py = read_fq(&input[FQ_LEN..2 * FQ_LEN])?; + new_g1_point(px, py) +} + +/// Encodes a G1 point into a byte array. +/// +/// Converts a G1 point in Jacobian coordinates to affine coordinates and +/// serializes the x and y coordinates as big-endian byte arrays. +/// +/// Note: If the point is the point at infinity, this function returns +/// all zeroes. +#[inline] +pub(super) fn encode_g1_point(point: G1) -> [u8; G1_LEN] { + let mut output = [0u8; G1_LEN]; + + if let Some(point_affine) = AffineG1::from_jacobian(point) { + point_affine.x().to_big_endian(&mut output[..FQ_LEN]).unwrap(); + point_affine.y().to_big_endian(&mut output[FQ_LEN..]).unwrap(); + } + + output +} + +/// Reads a G2 point from the input slice. +/// +/// Parses a G2 point from a byte slice by reading four consecutive Fq field elements +/// representing the two Fq2 coordinates (x and y) of the G2 point. +/// +/// # Panics +/// +/// Panics if the input is not at least 128 bytes long. +#[inline] +pub(super) fn read_g2_point(input: &[u8]) -> Result { + let ba = read_fq2(&input[0..FQ2_LEN])?; + let bb = read_fq2(&input[FQ2_LEN..2 * FQ2_LEN])?; + new_g2_point(ba, bb) +} + +/// Reads a scalar from the input slice +/// +/// Note: The scalar does not need to be canonical. +/// +/// # Panics +/// +/// If `input.len()` is not equal to [`SCALAR_LEN`]. +#[inline] +pub(super) fn read_scalar(input: &[u8]) -> bn::Fr { + assert_eq!( + input.len(), + SCALAR_LEN, + "unexpected scalar length. got {}, expected {SCALAR_LEN}", + input.len() + ); + // `Fr::from_slice` can only fail when the length is not `SCALAR_LEN`. + bn::Fr::from_slice(input).unwrap() +} + +/// Performs point addition on two G1 points. +#[inline] +pub(super) fn g1_point_add(p1: G1, p2: G1) -> G1 { + p1 + p2 +} + +/// Performs a G1 scalar multiplication. +#[inline] +pub(super) fn g1_point_mul(p: G1, fr: bn::Fr) -> G1 { + p * fr +} + +/// pairing_check performs a pairing check on a list of G1 and G2 point pairs and +/// returns true if the result is equal to the identity element. +/// +/// Note: If the input is empty, this function returns true. +/// This is different to EIP2537 which disallows the empty input. +#[inline] +pub(super) fn pairing_check(pairs: &[(G1, G2)]) -> bool { + if pairs.is_empty() { + return true; + } + bn::pairing_batch(pairs) == Gt::one() +} diff --git a/src/precompile/hash.rs b/src/precompile/hash.rs index 4d14908..73f3674 100644 --- a/src/precompile/hash.rs +++ b/src/precompile/hash.rs @@ -1,35 +1,45 @@ use super::precompile_not_implemented; -use revm::{ - precompile::{hash::sha256_run, u64_to_address, PrecompileWithAddress}, - primitives::Address, -}; +use revm::{precompile::PrecompileWithAddress, primitives::Address}; pub mod sha256 { + use revm::precompile::PrecompileResult; use super::*; // CONSTANTS // ------------------------------------------------------------------------------------------------ - /// The SHA256 precompile index. - const SHA256_PRECOMPILE_INDEX: u64 = 2; - /// The SHA256 precompile address. - const SHA256_PRECOMPILE_ADDRESS: Address = u64_to_address(SHA256_PRECOMPILE_INDEX); + pub const ADDRESS: Address = revm::precompile::hash::SHA256.0; // SHA256 SHANGHAI PRECOMPILE // -------------------------------------------------------------------------------------------- /// The shanghai SHA256 precompile implementation with address. - pub const SHA256_SHANGHAI: PrecompileWithAddress = - precompile_not_implemented(SHA256_PRECOMPILE_ADDRESS); + pub const SHANGHAI: PrecompileWithAddress = precompile_not_implemented(ADDRESS); // SHA256 BERNOULLI PRECOMPILE // -------------------------------------------------------------------------------------------- /// The bernoulli SHA256 precompile implementation with address. - pub const SHA256_BERNOULLI: PrecompileWithAddress = - PrecompileWithAddress(SHA256_PRECOMPILE_ADDRESS, sha256_run); + pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress(ADDRESS, run); + + pub fn run(input: &[u8], gas_limit: u64) -> PrecompileResult { + cfg_if::cfg_if! { + if #[cfg(feature = "openvm")] { + use revm::precompile::{calc_linear_cost_u32, PrecompileError, PrecompileOutput}; + let cost = calc_linear_cost_u32(input.len(), 60, 12); + if cost > gas_limit { + Err(PrecompileError::OutOfGas) + } else { + let output = openvm_sha2::sha256(input); + Ok(PrecompileOutput::new(cost, output.to_vec().into())) + } + } else { + return revm::precompile::hash::sha256_run(input, gas_limit); + } + } + } } pub mod ripemd160 { @@ -38,11 +48,8 @@ pub mod ripemd160 { // CONSTANTS // -------------------------------------------------------------------------------------------- - /// The RIPEMD160 precompile index. - const RIPEMD160_PRECOMPILE_INDEX: u64 = 3; - /// The RIPEMD160 precompile address. - const RIPEMD160_PRECOMPILE_ADDRESS: Address = u64_to_address(RIPEMD160_PRECOMPILE_INDEX); + const ADDRESS: Address = revm::precompile::hash::RIPEMD160.0; // RIPEMD160 SHANGHAI PRECOMPILE // -------------------------------------------------------------------------------------------- @@ -51,6 +58,5 @@ pub mod ripemd160 { /// /// This precompile is not implemented and will return `PrecompileError::Other("Precompile not /// implemented".into())`. - pub const RIPEMD160_SHANGHAI: PrecompileWithAddress = - precompile_not_implemented(RIPEMD160_PRECOMPILE_ADDRESS); + pub const SHANGHAI: PrecompileWithAddress = precompile_not_implemented(ADDRESS); } diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 607a713..4b67558 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -58,12 +58,12 @@ pub(crate) fn pre_bernoulli() -> &'static Precompiles { precompiles.extend([ precompile::secp256k1::ECRECOVER, - hash::sha256::SHA256_SHANGHAI, - hash::ripemd160::RIPEMD160_SHANGHAI, + hash::sha256::SHANGHAI, + hash::ripemd160::SHANGHAI, precompile::identity::FUN, modexp::BERNOULLI, - precompile::bn128::add::ISTANBUL, - precompile::bn128::mul::ISTANBUL, + bn128::add::ISTANBUL, + bn128::mul::ISTANBUL, bn128::pair::BERNOULLI, blake2::SHANGHAI, ]); @@ -77,7 +77,7 @@ pub(crate) fn bernoulli() -> &'static Precompiles { static INSTANCE: OnceBox = OnceBox::new(); INSTANCE.get_or_init(|| { let mut precompiles = pre_bernoulli().clone(); - precompiles.extend([hash::sha256::SHA256_BERNOULLI]); + precompiles.extend([hash::sha256::BERNOULLI]); Box::new(precompiles) }) } @@ -149,7 +149,7 @@ impl Default for ScrollPrecompileProvider { #[cfg(test)] mod tests { use super::*; - use crate::precompile::bn128::pair::BN128_PAIRING_PRECOMPILE_ADDRESS; + use crate::precompile::bn128::pair; use revm::primitives::hex; #[test] @@ -162,12 +162,12 @@ mod tests { .unwrap(); // Euclid version should reject this input - let f = euclid().get(&BN128_PAIRING_PRECOMPILE_ADDRESS).expect("precompile exists"); + let f = euclid().get(&pair::ADDRESS).expect("precompile exists"); let outcome = f(&input, u64::MAX); assert!(outcome.is_err()); // Feynman version should accept this input - let f = feynman().get(&BN128_PAIRING_PRECOMPILE_ADDRESS).expect("precompile exists"); + let f = feynman().get(&pair::ADDRESS).expect("precompile exists"); let outcome = f(&input, u64::MAX).expect("call succeeds"); assert_eq!(outcome.bytes, expected); } diff --git a/src/precompile/modexp.rs b/src/precompile/modexp.rs index a005092..ef42eae 100644 --- a/src/precompile/modexp.rs +++ b/src/precompile/modexp.rs @@ -1,7 +1,6 @@ use revm::{ precompile::{ modexp::{berlin_gas_calc, run_inner}, - u64_to_address, utilities::right_pad_with_offset, PrecompileError, PrecompileResult, PrecompileWithAddress, }, @@ -11,11 +10,8 @@ use revm::{ // CONSTANTS // ================================================================================================ -/// The MODEXP precompile index. -const MODEXP_PRECOMPILE_INDEX: u64 = 5; - /// The MODEXP precompile address. -const MODEXP_PRECOMPILE_ADDRESS: Address = u64_to_address(MODEXP_PRECOMPILE_INDEX); +const ADDRESS: Address = revm::precompile::modexp::OSAKA.0; /// The maximum length of the input for the MODEXP precompile. const SCROLL_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); @@ -24,8 +20,7 @@ const SCROLL_LEN_LIMIT: U256 = U256::from_limbs([32, 0, 0, 0]); // ================================================================================================ /// The bernoulli MODEXP precompile implementation with address. -pub const BERNOULLI: PrecompileWithAddress = - PrecompileWithAddress(MODEXP_PRECOMPILE_ADDRESS, bernoulli_run); +pub const BERNOULLI: PrecompileWithAddress = PrecompileWithAddress(ADDRESS, bernoulli_run); /// The bernoulli MODEXP precompile implementation. /// From b7a331814d336aa22a6f42da5903b25592c9036d Mon Sep 17 00:00:00 2001 From: lightsing Date: Thu, 19 Jun 2025 19:17:59 +0800 Subject: [PATCH 2/6] arkworks as default --- Cargo.toml | 11 +++++------ src/precompile/bn128/mod.rs | 4 +--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 35b6f9f..b2c5dde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,10 @@ serde = { version = "1.0", features = ["derive"], optional = true, default-featu bn = { package = "substrate-bn", version = "0.6", default-features = false, optional = true } # Use arkworks implementation for eip1962 -ark-bn254 = { version = "0.5", default-features = false, optional = true } -ark-ec = { version = "0.5", default-features = false, optional = true } -ark-ff = { version = "0.5", default-features = false, optional = true } -ark-serialize = { version = "0.5", default-features = false, optional = true } +ark-bn254 = { version = "0.5", default-features = false, features = ["curve"] } +ark-ec = { version = "0.5", default-features = false } +ark-ff = { version = "0.5", default-features = false, features = ["asm"] } +ark-serialize = { version = "0.5", default-features = false } # openvm @@ -33,7 +33,7 @@ openvm-sha2 = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } [features] -default = ["std", "c-kzg", "secp256k1", "portable", "blst", "arkworks"] +default = ["std", "c-kzg", "secp256k1", "portable", "blst"] std = ["serde?/std", "revm/std"] hashbrown = ["revm/hashbrown"] serde = ["dep:serde", "revm/serde"] @@ -48,7 +48,6 @@ blst = ["revm/blst"] # bn128 related bn = ["revm/bn", "dep:bn"] -arkworks = ["dep:ark-bn254", "dep:ark-ec", "dep:ark-ff", "dep:ark-serialize", "ark-bn254/curve", "ark-ff/asm"] openvm = [ "dep:openvm-ecc-guest", diff --git a/src/precompile/bn128/mod.rs b/src/precompile/bn128/mod.rs index 4ac7840..928a939 100644 --- a/src/precompile/bn128/mod.rs +++ b/src/precompile/bn128/mod.rs @@ -18,14 +18,12 @@ cfg_if::cfg_if! { encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; - } else if #[cfg(feature = "arkworks")] { + } else { mod arkworks; use arkworks::{ encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; - } else { - compile_error!("At least one of the features 'bn', 'arkworks', or 'openvm' must be enabled for bn128 precompiles."); } } From 57254bd1aa63f5a86c8be298adb0950a3a048c68 Mon Sep 17 00:00:00 2001 From: lightsing Date: Thu, 19 Jun 2025 19:27:28 +0800 Subject: [PATCH 3/6] gate openvm behind zkvm --- Cargo.toml | 17 +++--- src/precompile/bn128/mod.rs | 100 ++++++++++++------------------------ src/precompile/hash.rs | 6 +-- 3 files changed, 44 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2c5dde..95e6eb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,11 @@ ark-ec = { version = "0.5", default-features = false } ark-ff = { version = "0.5", default-features = false, features = ["asm"] } ark-serialize = { version = "0.5", default-features = false } - # openvm -openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } -openvm-sha2 = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } -openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", optional = true } +[target.'cfg(all(target_os = "zkvm", not(target_vendor = "succinct"), target_arch = "riscv32"))'.dependencies] +openvm-ecc-guest = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7" } +openvm-sha2 = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7" } +openvm-pairing = { git = "https://github.com/openvm-org/openvm.git", rev = "9ef822c947f14eb436e293971c969cfcb688dbe7", features = ["bn254"] } [features] default = ["std", "c-kzg", "secp256k1", "portable", "blst"] @@ -48,10 +48,7 @@ blst = ["revm/blst"] # bn128 related bn = ["revm/bn", "dep:bn"] +openvm = [] -openvm = [ - "dep:openvm-ecc-guest", - "dep:openvm-sha2", - "dep:openvm-pairing", - "openvm-pairing/bn254", -] +[lints.rust] +unexpected-cfgs = "allow" diff --git a/src/precompile/bn128/mod.rs b/src/precompile/bn128/mod.rs index 928a939..37fe31c 100644 --- a/src/precompile/bn128/mod.rs +++ b/src/precompile/bn128/mod.rs @@ -6,18 +6,20 @@ use revm::precompile::{ use std::vec::Vec; cfg_if::cfg_if! { - if #[cfg(feature = "openvm")] { + if #[cfg(all(target_os = "zkvm", not(target_vendor = "succinct"), target_arch = "riscv32", feature = "openvm"))] { mod openvm; use openvm::{ encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; + use {bn as _, ark_bn254 as _, ark_ec as _, ark_ff as _, ark_serialize as _}; } else if #[cfg(feature = "bn")]{ mod substrate; use substrate::{ encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; + use {ark_bn254 as _, ark_ec as _, ark_ff as _, ark_serialize as _}; } else { mod arkworks; use arkworks::{ @@ -237,13 +239,13 @@ mod tests { 07c2b7f58a84bd6145f00c9c2bc0bb1a187f20ff2c92963a88019e7c6a014eed\ 06614e20c147e940f2d70da3f74c9a17df361706a4485c742bd6788478fa17d7", ) - .unwrap(); + .unwrap(); let expected = hex::decode( "\ 2243525c5efd4b9c3d3c45ac0ca3fe4dd85e830a4ce6b65fa1eeaee202839703\ 301d1d33be6da8e509df21cc35964723180eed7532537db9ae5e7d48f195c915", ) - .unwrap(); + .unwrap(); let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); assert_eq!(outcome.bytes, expected); @@ -256,13 +258,13 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let expected = hex::decode( "\ 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); assert_eq!(outcome.bytes, expected); @@ -275,7 +277,7 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 499); @@ -288,7 +290,7 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let outcome = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500).unwrap(); assert_eq!(outcome.bytes, expected); @@ -301,13 +303,10 @@ mod tests { 1111111111111111111111111111111111111111111111111111111111111111\ 1111111111111111111111111111111111111111111111111111111111111111", ) - .unwrap(); + .unwrap(); let res = run_add(&input, BYZANTIUM_ADD_GAS_COST, 500); - assert!(matches!( - res, - Err(PrecompileError::Bn128AffineGFailedToCreate) - )); + assert!(matches!(res, Err(PrecompileError::Bn128AffineGFailedToCreate))); } #[test] @@ -318,13 +317,13 @@ mod tests { 21611ce0a6af85915e2f1d70300909ce2e49dfad4a4619c8390cae66cefdb204\ 00000000000000000000000000000000000000000000000011138ce750fa15c2", ) - .unwrap(); + .unwrap(); let expected = hex::decode( "\ 070a8d6a982153cae4be29d434e8faef8a47b274a053f5a4ee2a6c9c13c31e5c\ 031b8ce914eba3a9ffb989f9cdd5b0f01943074bf4f0f315690ec3cec6981afc", ) - .unwrap(); + .unwrap(); let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); assert_eq!(outcome.bytes, expected); @@ -336,7 +335,7 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0200000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 39_999); assert!(matches!(res, Err(PrecompileError::OutOfGas))); @@ -348,13 +347,13 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0200000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let expected = hex::decode( "\ 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); assert_eq!(outcome.bytes, expected); @@ -366,7 +365,7 @@ mod tests { 0000000000000000000000000000000000000000000000000000000000000000\ 0000000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let outcome = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000).unwrap(); assert_eq!(outcome.bytes, expected); @@ -378,13 +377,10 @@ mod tests { 1111111111111111111111111111111111111111111111111111111111111111\ 0f00000000000000000000000000000000000000000000000000000000000000", ) - .unwrap(); + .unwrap(); let res = run_mul(&input, BYZANTIUM_MUL_GAS_COST, 40_000); - assert!(matches!( - res, - Err(PrecompileError::Bn128AffineGFailedToCreate) - )); + assert!(matches!(res, Err(PrecompileError::Bn128AffineGFailedToCreate))); } #[test] @@ -404,18 +400,13 @@ mod tests { 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", ) - .unwrap(); + .unwrap(); let expected = hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = run_pair( - &input, - BYZANTIUM_PAIR_PER_POINT, - BYZANTIUM_PAIR_BASE, - 260_000, - ) - .unwrap(); + let outcome = + run_pair(&input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000).unwrap(); assert_eq!(outcome.bytes, expected); // Out of gas test @@ -434,14 +425,9 @@ mod tests { 090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b\ 12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", ) - .unwrap(); - - let res = run_pair( - &input, - BYZANTIUM_PAIR_PER_POINT, - BYZANTIUM_PAIR_BASE, - 259_999, - ); + .unwrap(); + + let res = run_pair(&input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 259_999); assert!(matches!(res, Err(PrecompileError::OutOfGas))); // No input test @@ -450,13 +436,8 @@ mod tests { hex::decode("0000000000000000000000000000000000000000000000000000000000000001") .unwrap(); - let outcome = run_pair( - &input, - BYZANTIUM_PAIR_PER_POINT, - BYZANTIUM_PAIR_BASE, - 260_000, - ) - .unwrap(); + let outcome = + run_pair(&input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000).unwrap(); assert_eq!(outcome.bytes, expected); // Point not on curve fail @@ -469,18 +450,10 @@ mod tests { 1111111111111111111111111111111111111111111111111111111111111111\ 1111111111111111111111111111111111111111111111111111111111111111", ) - .unwrap(); - - let res = run_pair( - &input, - BYZANTIUM_PAIR_PER_POINT, - BYZANTIUM_PAIR_BASE, - 260_000, - ); - assert!(matches!( - res, - Err(PrecompileError::Bn128AffineGFailedToCreate) - )); + .unwrap(); + + let res = run_pair(&input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000); + assert!(matches!(res, Err(PrecompileError::Bn128AffineGFailedToCreate))); // Invalid input length let input = hex::decode( @@ -490,14 +463,9 @@ mod tests { 111111111111111111111111111111\ ", ) - .unwrap(); - - let res = run_pair( - &input, - BYZANTIUM_PAIR_PER_POINT, - BYZANTIUM_PAIR_BASE, - 260_000, - ); + .unwrap(); + + let res = run_pair(&input, BYZANTIUM_PAIR_PER_POINT, BYZANTIUM_PAIR_BASE, 260_000); assert!(matches!(res, Err(PrecompileError::Bn128PairLength))); } } diff --git a/src/precompile/hash.rs b/src/precompile/hash.rs index 73f3674..3162b6e 100644 --- a/src/precompile/hash.rs +++ b/src/precompile/hash.rs @@ -3,8 +3,8 @@ use super::precompile_not_implemented; use revm::{precompile::PrecompileWithAddress, primitives::Address}; pub mod sha256 { - use revm::precompile::PrecompileResult; use super::*; + use revm::precompile::PrecompileResult; // CONSTANTS // ------------------------------------------------------------------------------------------------ @@ -26,7 +26,7 @@ pub mod sha256 { pub fn run(input: &[u8], gas_limit: u64) -> PrecompileResult { cfg_if::cfg_if! { - if #[cfg(feature = "openvm")] { + if #[cfg(all(target_os = "zkvm", not(target_vendor = "succinct"), target_arch = "riscv32", feature = "openvm"))] { use revm::precompile::{calc_linear_cost_u32, PrecompileError, PrecompileOutput}; let cost = calc_linear_cost_u32(input.len(), 60, 12); if cost > gas_limit { @@ -36,7 +36,7 @@ pub mod sha256 { Ok(PrecompileOutput::new(cost, output.to_vec().into())) } } else { - return revm::precompile::hash::sha256_run(input, gas_limit); + revm::precompile::hash::sha256_run(input, gas_limit) } } } From 2139aefbb92bb4fe99723f20bacf4380d808d6df Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 20 Jun 2025 11:39:24 +0800 Subject: [PATCH 4/6] make pub --- src/precompile/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/precompile/mod.rs b/src/precompile/mod.rs index 4b67558..2edad38 100644 --- a/src/precompile/mod.rs +++ b/src/precompile/mod.rs @@ -11,10 +11,10 @@ use revm::{ }; use revm_primitives::hardfork::SpecId; -mod blake2; -mod bn128; -mod hash; -mod modexp; +pub mod blake2; +pub mod bn128; +pub mod hash; +pub mod modexp; /// Provides Scroll precompiles, modifying any relevant behaviour. #[derive(Debug, Clone)] From c4c396e40db35279ec614b42d91743d36b547298 Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 20 Jun 2025 11:39:29 +0800 Subject: [PATCH 5/6] fix --- src/precompile/bn128/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/precompile/bn128/mod.rs b/src/precompile/bn128/mod.rs index 37fe31c..ba222e3 100644 --- a/src/precompile/bn128/mod.rs +++ b/src/precompile/bn128/mod.rs @@ -12,7 +12,9 @@ cfg_if::cfg_if! { encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; - use {bn as _, ark_bn254 as _, ark_ec as _, ark_ff as _, ark_serialize as _}; + #[cfg(feature = "bn")] + use bn as _; + use {ark_bn254 as _, ark_ec as _, ark_ff as _, ark_serialize as _}; } else if #[cfg(feature = "bn")]{ mod substrate; use substrate::{ From aa8cfdc37cbef076c1d85bfb84edf42da9652022 Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 20 Jun 2025 11:40:58 +0800 Subject: [PATCH 6/6] disable unused_crate_dependencies on zkvm --- src/lib.rs | 2 +- src/precompile/bn128/mod.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 068c990..bf309a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(not(any(test, target_os = "zkvm")), warn(unused_crate_dependencies))] #![cfg_attr(not(feature = "std"), no_std)] #[cfg(not(feature = "std"))] diff --git a/src/precompile/bn128/mod.rs b/src/precompile/bn128/mod.rs index ba222e3..3073796 100644 --- a/src/precompile/bn128/mod.rs +++ b/src/precompile/bn128/mod.rs @@ -12,9 +12,6 @@ cfg_if::cfg_if! { encode_g1_point, g1_point_add, g1_point_mul, pairing_check, read_g1_point, read_g2_point, read_scalar, }; - #[cfg(feature = "bn")] - use bn as _; - use {ark_bn254 as _, ark_ec as _, ark_ff as _, ark_serialize as _}; } else if #[cfg(feature = "bn")]{ mod substrate; use substrate::{