Skip to content

Commit

Permalink
precompiles: Implement KZG proof verification (aka "point evaluation") (
Browse files Browse the repository at this point in the history
#979)

Implement the KZG proof verification using the blst library.
Add native `point_evaluation` implementation.
Add benchmarks and report the comparison with Silkworm's implementation (see PR).
  • Loading branch information
chfast authored Sep 19, 2024
2 parents edfe00d + 85e7ff7 commit 4a3cb41
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 57 deletions.
2 changes: 2 additions & 0 deletions lib/evmone_precompiles/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ target_sources(
secp256k1.cpp
sha256.hpp
sha256.cpp
kzg.hpp
kzg.cpp
)
168 changes: 168 additions & 0 deletions lib/evmone_precompiles/kzg.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2024 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "kzg.hpp"
#include <blst.h>
#include <algorithm>
#include <optional>
#include <span>

namespace evmone::crypto
{
namespace
{
/// The field element 1 in Montgomery form.
constexpr blst_fp ONE = {0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba,
0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493};

/// The negation of the subgroup G1 generator -[1]₁ (Jacobian coordinates in Montgomery form).
constexpr blst_p1 G1_GENERATOR_NEGATIVE = {
{0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747,
0xedce6ecc21dbf440, 0x120177419e0bfb75},
{0xff526c2af318883a, 0x92899ce4383b0270, 0x89d7738d9fa9d055, 0x12caf35ba344c12a,
0x3cff1b76964b5317, 0x0e44d2ede9774430},
ONE};

/// The negation of the subgroup G2 generator -[1]₂ (Jacobian coordinates in Montgomery form).
constexpr blst_p2 G2_GENERATOR_NEGATIVE{
{{{0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9,
0x6f67b7631863366b, 0x058191924350bcd7},
{0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367,
0xc2b6ed0ef2158547, 0x11922a097360edf3}}},
{{{0x6d8bf5079fb65e61, 0xc52f05df531d63a5, 0x7f4a4d344ca692c9, 0xa887959b8577c95f,
0x4347fe40525c8734, 0x197d145bbaff0bb5},
{0x0c3e036d209afa4e, 0x0601d8f4863f9e23, 0xe0832636bacc0a84, 0xeb2def362a476f84,
0x64044f659f0ee1e9, 0x0ed54f48d5a1caa7}}},
{{ONE, {}}}};

/// The point from the G2 series, index 1 of the Ethereum KZG trusted setup,
/// i.e. [s]₂ where s is the trusted setup's secret.
/// Affine coordinates in Montgomery form.
/// The original value in compressed form (y-parity bit and Fp2 x coordinate)
/// is the ["g2_monomial"][1] of the JSON object found at
/// https://github.com/ethereum/consensus-specs/blob/dev/presets/mainnet/trusted_setups/trusted_setup_4096.json#L8200
constexpr blst_p2_affine KZG_SETUP_G2_1{
{{{0x6120a2099b0379f9, 0xa2df815cb8210e4e, 0xcb57be5577bd3d4f, 0x62da0ea89a0c93f8,
0x02e0ee16968e150d, 0x171f09aea833acd5},
{0x11a3670749dfd455, 0x04991d7b3abffadc, 0x85446a8e14437f41, 0x27174e7b4e76e3f2,
0x7bfa6dd397f60a20, 0x02fcc329ac07080f}}},
{{{0xaa130838793b2317, 0xe236dd220f891637, 0x6502782925760980, 0xd05c25f60557ec89,
0x6095767a44064474, 0x185693917080d405},
{0x549f9e175b03dc0a, 0x32c0c95a77106cfe, 0x64a74eae5705d080, 0x53deeaf56659ed9e,
0x09a1d368508afb93, 0x12cf3a4525b5e9bd}}}};

/// Load and validate an element from the group order field.
std::optional<blst_scalar> validate_scalar(std::span<const std::byte, 32> b) noexcept
{
blst_scalar v;
blst_scalar_from_bendian(&v, reinterpret_cast<const uint8_t*>(b.data()));
return blst_scalar_fr_check(&v) ? std::optional{v} : std::nullopt;
}

/// Uncompress and validate a point from G1 subgroup.
std::optional<blst_p1_affine> validate_G1(std::span<const std::byte, 48> b) noexcept
{
blst_p1_affine r;
if (blst_p1_uncompress(&r, reinterpret_cast<const uint8_t*>(b.data())) != BLST_SUCCESS)
return std::nullopt;

// Subgroup check is required by the spec but there are no test vectors
// with points outside G1 which would satisfy the final pairings check.
if (!blst_p1_affine_in_g1(&r))
return std::nullopt;
return r;
}

/// Add two points from E1 and convert the result to affine form.
/// The conversion to affine is very costly so use only if the affine of the result is needed.
blst_p1_affine add_or_double(const blst_p1_affine& p, const blst_p1& q) noexcept
{
blst_p1 r;
blst_p1_add_or_double_affine(&r, &q, &p);
blst_p1_affine ra;
blst_p1_to_affine(&ra, &r);
return ra;
}

blst_p1 mult(const blst_p1& p, const blst_scalar& v) noexcept
{
blst_p1 r;
blst_p1_mult(&r, &p, v.b, BLS_MODULUS_BITS);
return r;
}

/// Add two points from E2 and convert the result to affine form.
/// The conversion to affine is very costly so use only if the affine of the result is needed.
blst_p2_affine add_or_double(const blst_p2_affine& p, const blst_p2& q) noexcept
{
blst_p2 r;
blst_p2_add_or_double_affine(&r, &q, &p);
blst_p2_affine ra;
blst_p2_to_affine(&ra, &r);
return ra;
}

blst_p2 mult(const blst_p2& p, const blst_scalar& v) noexcept
{
blst_p2 r;
blst_p2_mult(&r, &p, v.b, BLS_MODULUS_BITS);
return r;
}

bool pairings_verify(
const blst_p1_affine& a1, const blst_p1_affine& b1, const blst_p2_affine& b2) noexcept
{
blst_fp12 left;
blst_aggregated_in_g1(&left, &a1);
blst_fp12 right;
blst_miller_loop(&right, &b2, &b1);
return blst_fp12_finalverify(&left, &right);
}
} // namespace

bool kzg_verify_proof(const std::byte versioned_hash[VERSIONED_HASH_SIZE], const std::byte z[32],
const std::byte y[32], const std::byte commitment[48], const std::byte proof[48]) noexcept
{
std::byte computed_versioned_hash[32];
sha256(computed_versioned_hash, commitment, 48);
computed_versioned_hash[0] = VERSIONED_HASH_VERSION_KZG;
if (!std::ranges::equal(std::span{versioned_hash, 32}, computed_versioned_hash))
return false;

// Load and validate scalars z and y.
// TODO(C++26): The span construction can be done as std::snap(z, std::c_<32>).
const auto zz = validate_scalar(std::span<const std::byte, 32>{z, 32});
if (!zz)
return false;
const auto yy = validate_scalar(std::span<const std::byte, 32>{y, 32});
if (!yy)
return false;

// Uncompress and validate the points C (representing the polynomial commitment)
// and Pi (representing the proof). They both are valid to be points at infinity
// when they prove a commitment to a constant polynomial,
// see https://hackmd.io/@kevaundray/kzg-is-zero-proof-sound
const auto C = validate_G1(std::span<const std::byte, 48>{commitment, 48});
if (!C)
return false;
const auto Pi = validate_G1(std::span<const std::byte, 48>{proof, 48});
if (!Pi)
return false;

// Compute -Y as [y * -1]₁.
const auto neg_Y = mult(G1_GENERATOR_NEGATIVE, *yy);

// Compute C - Y. It can happen that C == -Y so doubling may be needed.
const auto C_sub_Y = add_or_double(*C, neg_Y);

// Compute -Z as [z * -1]₂.
const auto neg_Z = mult(G2_GENERATOR_NEGATIVE, *zz);

// Compute X - Z which is [s - z]₂.
const auto X_sub_Z = add_or_double(KZG_SETUP_G2_1, neg_Z);

// e(C - [y]₁, [1]₂) =? e(Pi, [s - z]₂)
return pairings_verify(C_sub_Y, *Pi, X_sub_Z);
}
} // namespace evmone::crypto
32 changes: 32 additions & 0 deletions lib/evmone_precompiles/kzg.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// evmone: Fast Ethereum Virtual Machine implementation
// Copyright 2024 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#pragma once
#include "sha256.hpp"
#include <intx/intx.hpp>

namespace evmone::crypto
{
using namespace intx::literals;

/// Length (in bytes) of the versioned hash (based on SHA256).
constexpr auto VERSIONED_HASH_SIZE = SHA256_HASH_SIZE;

/// The KZG version number of the versioned hash.
constexpr std::byte VERSIONED_HASH_VERSION_KZG{0x01};

/// An EIP-4844 parameter.
constexpr auto FIELD_ELEMENTS_PER_BLOB = 4096_u256;

/// Scalar field modulus of BLS12-381.
constexpr auto BLS_MODULUS =
52435875175126190479447740508185965837690552500527637822603658699938581184513_u256;

/// Number of significant bits of the BLS_MODULUS.
constexpr size_t BLS_MODULUS_BITS = 255;
static_assert((BLS_MODULUS >> BLS_MODULUS_BITS) == 0);

bool kzg_verify_proof(const std::byte versioned_hash[VERSIONED_HASH_SIZE], const std::byte z[32],
const std::byte y[32], const std::byte commitment[48], const std::byte proof[48]) noexcept;
} // namespace evmone::crypto
24 changes: 24 additions & 0 deletions test/precompiles_bench/precompiles_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ template <>
constexpr auto analyze<PrecompileId::ecmul> = ecmul_analyze;
template <>
[[maybe_unused]] constexpr auto analyze<PrecompileId::ecpairing> = ecpairing_analyze;
template <>
constexpr auto analyze<PrecompileId::point_evaluation> = point_evaluation_analyze;

template <PrecompileId>
const inline std::array inputs{0};
Expand Down Expand Up @@ -120,6 +122,22 @@ const inline std::array inputs<PrecompileId::ecpairing>{
"1bce8fa5482d9e50dfea4fb1dd94522e9b716f44d9918d6708fac0f0881938632a4338da2f280b176ab6924abb4775b55b508a8e36fff191023fdc5e99ba928b13bb5bd8d9a2b2822aacf155d7b22beebd4fe1910bd0dbb2834f2dc949f65cf60fc09cf79df13a761e90268de37a3595a7e9dc971fcaf7b26b784559036dfbb00c4f7aaa0d95a7cdeb1854ddef2a87b9393b6ce296e3c0ec20af93a0fde37abb2b8903140adc77bc3ed134c34571e16e949eb7d4a827da48859a9614aba82f722d4d9aa7e302d9df41749d5507949d05dbea33fbb16c643b22f599a2be6df2e214bedd503c37ceb061d8ec60209fe345ce89830a19230301f076caff004d19260967032fcbf776d1afc985f88877f182d38480a653f2decaa9794cbc3bf3060c0e187847ad4c798374d0d6732bf501847dd68bc0e071241e0213bc7fc13db7ab304cfbd1e08a704a99f5e847d93f8c3caafddec46b7a0d379da69a4d112346a71739c1b1a457a8c7313123d24d2f9192f896b7c63eea05a9d57f06547ad0cec829ad8a4a19c8558b7586b3748ef5315ba4520e87bf714177ded2b8b0dee8a03e067cb77535c18066291a1f20a16b7fb8f5d609ad5be42d1598bca446703d74c6198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c21800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa2d1b4674ee05363271f65f69fb5aa3d24145eb76f76bf49477e7950b8b4a050c18a6763f1cbe6d2334efcd94661dd802125011c7aba93167d8e25320640621882c4e22719c67bf7bddedcc98face8265be63ccdcef8139c221f21326aa48d1bd0700cbe071c394f095926c90427fe8a412216739e7633acb7c02cd719c12ecb92b34f9525b49f109aed50c68544acf38750b4c7334709d328de3973744953fcb0923b1a411590566dc946db78f12e46e568b91150d4b929ff7b4b5aba0fbab7b"_hex,
};

template <>
const inline std::array inputs<PrecompileId::point_evaluation>{
// Inputs taken randomly from Mainnet
// https://etherscan.io/address/0x000000000000000000000000000000000000000a/advanced#internaltx
"012b08a0504a63aac18383db69fe6b52fc833e3d060b87c2726c4140c909d91807dddd3c80995c2bb3012943e2036e77490b1f6ddc58ca39a4fb4f3225ae56ab11dc2c4d89f777f0f5c2a51f45b73ff1538761f9cf23ed74c74472fea625ad8bace1db77e25ceb316d914182e05dd810f112352e1d6ed9e47af28e2f64e22b94c411794359c2273bc10bc0390963fb1a97bb642307bfa4424c66bd90ecc0ecffd5045e492b40304df20346693db7450457e2c72588a6a2b1a16909e2ab1e6284"_hex,
"019cd755316533108b9eade41e35a16442ae76acd5b7d4e8903ecb9d9f48348a00000000000000000000000000000000dd372dcb4e5565861fc29cfb12f4373861e6e2dfca75084191a505f7988db8e82a4a4a09734b6fd7677d590a1cb512768c381fc4957f406ef89996d9dfa1d39b5c8d1368569e56fd61036c537400a3f4515eeb0c4d183142daa2c30423e0c3fa84667445c1669d3a3e3fce8a1144811e4452841399318c21cca9d20c91fb162929c4e96d391b70158bcd4c69b682b272"_hex,
"0187576b6a38dd4ca8ce00e35dd12d1dd91e06ba3bde49d01568103d826d59732fd172adc351401950681fb66f9464410b15437ab00599aede3f90d0d9552bd162920cfc9b91d123f2c24034006fc9b7f5217cfae1022be231b6bd37262fda83ac48e50cbfeeee227daee56a8bff2f96ede7757b6f2598bd40b14d75b04c07a92299443d1eabe857f57fc95b0ca8b121adff55fad542926063245c402008a846c60eeca2e419a3e9e12ddfc0184a606b31d39268d8b580b57ac274501858bdb5"_hex,
"0145e6c573f2f24f95eeddb687df7da5da51ae1d58bc3691ff52c8d667e704db0000000000000000000000000000000076231706fc7f1f5cd05a88ed538b74ab2672abe246a6042b24c709294ea2e85672a6edc7aee8ab556adfd7af889086afa1bf23edb5e0300f4d924350197c497f084af76365a6209c4b93f00b1ffc803fb7847ca51f4fc1df4fdb9298c16d6a1ea038d42f9ffa40e872eb722e095ea19ff4f2e891a2676b93905476bc8186b741f64c7281f7bba1a14e086316bf5e4b67"_hex,
"0129c00365c61f29b376a0ff296052604481bce870816c0b1d8a8643d5903c7e000000000000000000000000000000000ae90ade08feef8d4531b692a6ea98ff07cee3b22e47da139784b96a01c6f2291ecd756cd520c10b4c86db65c7ab754a8c4174fa25da26bb3121232ee8ab7d7bdacf4662971bf9fdc92a374b98dc8ca844ae3950ff5bdd1d956b9169666fcaefb44a2aae8ddcbb7be512604565391bc1d5468488c11dcf27be9f2003a651688d660d82060c225c078192ecb8c25ba802"_hex,
"018490afa27e04a58f6be1ccd8376ad77819954b5c2c950eb88c5e23413e75356056276dbe61c3076bb23c172adb59beb669713669f42036f305d33e39405e4747420b62257ed239139f68d6f70904a3970809e2c159a5d869f2fe3d28f876ef8d6124ae4698b9b10d5333ed73fedde92b76d7009febd0bbd3eef4748bcfbf8080c452fc31461045e03e24d1a4db246a94f75bad459310cec7f3fe8f9798ba615fe3dffba3dd074d4b906e5c0b1a0db916a519f6251ce9b0f327e0628897f271"_hex,
"01ae47a1b7702fdc67641c971a8a6b3296f9273667ac2fc540934aedd8d7a8ef3e3af8f3f90a55415e6e8b50e2b86b1b227de39feef2b98d840f39774220ca492345c4e5ba1ed2c0e414bd22fc9155c786e667b009272c6ff1596329668288b0abd3f3aff02af02fdff85f322067004c0a44ea8626d033f3c95470bc7fdda33a3f2ecf49ea24b94aba043b295b1ba82d8aa2ad2b1b9b2b0556679828f0e64a27988f1d9ebda3b00b9c6dbdee6e50aafdffd092583aeba3d42eb48d0cb09da2cc"_hex,
"015234bf406d81ea1d44ef75f19f9932b426fb8c76ffffe93906d6f7dce0d04900000000000000000000000000000000c3e098d7dad534d2d51cd14da7e73cf442f7483e30344d2efe4d2c7ea3d8b67da3131476983d45d625b0ffabe960395489f402c36d9837eb7c0872a223dd64233e5ba7c6a5dd72de7f8c8406f3f87e5e31df83132ef527aa9dfd3a2f56c98799980fd810ab52f4edd43347a64889d2f9850acbe16d9494478c1a462b69a099a960a98f48971d50b7453e2a03227577b1"_hex,
"01da11f18d57a31a52071af0062552bca6477958994aa068473a7182241ba65c0000000000000000000000000000000061acc5cf049e854931e084b9bc044ecb4bda7c50de18c50ec84b6d13009bb56afa474010fed9a9c861fc76077eb0b327ad2e1b9700e19f435fda3ad6a53d855315cf61ee2cf24d80b9fbf5523ed5ce76a5267617a4b4bd63234d37430ff0274f8024e664b701b13589313f6b315d32122effb0224b223e055c520c3593df101fcb5c51f823d7eb9a2b882e4c9cb46e2b"_hex,
"01ff83fbe4488061b695d9e4632e9ab23c5b9dfb046241fac921659c2e3b6a4e5e88a0c20ef27ae5dea6e83a748115df152b207362c022a43c8ebbfbec73ea2a2b38376a1de4ed9abd35175ac422af7df2c6ddf1503979964b1d217747c108978969cc0fcbd47dba8b1e3cbba16920f3fc694fadbf203b55aa66aaf138a38ace946a7ccddc20f45d796cd60268df8a2487d06c11cdc50247c580c5dd25f6fb486053b73868038cb92c2ae6d429a7bde850177f31c9fde00ca824c8c59cd4ef49"_hex,
};

template <PrecompileId Id, ExecuteFn Fn>
void precompile(benchmark::State& state)
{
Expand Down Expand Up @@ -194,6 +212,12 @@ BENCHMARK_TEMPLATE(precompile, PrecompileId::ecpairing, libff);
#endif
} // namespace bench_ecpairing

namespace bench_kzg
{
constexpr auto evmone_blst = point_evaluation_execute;
BENCHMARK_TEMPLATE(precompile, PrecompileId::point_evaluation, evmone_blst);
} // namespace bench_kzg

} // namespace

BENCHMARK_MAIN();
26 changes: 25 additions & 1 deletion test/state/precompiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <evmone_precompiles/blake2b.hpp>
#include <evmone_precompiles/bls.hpp>
#include <evmone_precompiles/bn254.hpp>
#include <evmone_precompiles/kzg.hpp>
#include <evmone_precompiles/ripemd160.hpp>
#include <evmone_precompiles/secp256k1.hpp>
#include <evmone_precompiles/sha256.hpp>
Expand Down Expand Up @@ -382,6 +383,29 @@ ExecutionResult blake2bf_execute(const uint8_t* input, [[maybe_unused]] size_t i
return {EVMC_SUCCESS, sizeof(h)};
}

ExecutionResult point_evaluation_execute(const uint8_t* input, size_t input_size, uint8_t* output,
[[maybe_unused]] size_t output_size) noexcept
{
assert(output_size >= 64);
if (input_size != 192)
return {EVMC_PRECOMPILE_FAILURE, 0};

const auto r = crypto::kzg_verify_proof(reinterpret_cast<const std::byte*>(&input[0]),
reinterpret_cast<const std::byte*>(&input[32]),
reinterpret_cast<const std::byte*>(&input[64]),
reinterpret_cast<const std::byte*>(&input[96]),
reinterpret_cast<const std::byte*>(&input[96 + 48]));

if (!r)
return {EVMC_PRECOMPILE_FAILURE, 0};

// Return FIELD_ELEMENTS_PER_BLOB and BLS_MODULUS as padded 32 byte big endian values
// as required by the EIP-4844.
intx::be::unsafe::store(output, crypto::FIELD_ELEMENTS_PER_BLOB);
intx::be::unsafe::store(output + 32, crypto::BLS_MODULUS);
return {EVMC_SUCCESS, 64};
}

ExecutionResult bls12_g1add_execute(const uint8_t* input, size_t input_size, uint8_t* output,
[[maybe_unused]] size_t output_size) noexcept
{
Expand Down Expand Up @@ -528,7 +552,7 @@ inline constexpr auto traits = []() noexcept {
{ecmul_analyze, ecmul_execute},
{ecpairing_analyze, ecpairing_stub},
{blake2bf_analyze, blake2bf_execute},
{point_evaluation_analyze, point_evaluation_stub},
{point_evaluation_analyze, point_evaluation_execute},
{bls12_g1add_analyze, bls12_g1add_execute},
{bls12_g1mul_analyze, bls12_g1mul_execute},
{bls12_g1msm_analyze, bls12_g1msm_execute},
Expand Down
2 changes: 2 additions & 0 deletions test/state/precompiles_internal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ ExecutionResult ecmul_execute(
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
ExecutionResult blake2bf_execute(
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
ExecutionResult point_evaluation_execute(
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
ExecutionResult bls12_g1add_execute(
const uint8_t* input, size_t input_size, uint8_t* output, size_t output_size) noexcept;
ExecutionResult bls12_g1mul_execute(
Expand Down
Loading

0 comments on commit 4a3cb41

Please sign in to comment.