|
| 1 | +// evmone: Fast Ethereum Virtual Machine implementation |
| 2 | +// Copyright 2024 The evmone Authors. |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +#include "kzg.hpp" |
| 6 | +#include <blst.h> |
| 7 | +#include <algorithm> |
| 8 | +#include <optional> |
| 9 | +#include <span> |
| 10 | + |
| 11 | +namespace evmone::crypto |
| 12 | +{ |
| 13 | +namespace |
| 14 | +{ |
| 15 | +/// The field element 1 in Montgomery form. |
| 16 | +constexpr blst_fp ONE = {0x760900000002fffd, 0xebf4000bc40c0002, 0x5f48985753c758ba, |
| 17 | + 0x77ce585370525745, 0x5c071a97a256ec6d, 0x15f65ec3fa80e493}; |
| 18 | + |
| 19 | +/// The negation of the subgroup G1 generator -[1]₁ (Jacobian coordinates in Montgomery form). |
| 20 | +constexpr blst_p1 G1_GENERATOR_NEGATIVE = { |
| 21 | + {0x5cb38790fd530c16, 0x7817fc679976fff5, 0x154f95c7143ba1c1, 0xf0ae6acdf3d0e747, |
| 22 | + 0xedce6ecc21dbf440, 0x120177419e0bfb75}, |
| 23 | + {0xff526c2af318883a, 0x92899ce4383b0270, 0x89d7738d9fa9d055, 0x12caf35ba344c12a, |
| 24 | + 0x3cff1b76964b5317, 0x0e44d2ede9774430}, |
| 25 | + ONE}; |
| 26 | + |
| 27 | +/// The negation of the subgroup G2 generator -[1]₂ (Jacobian coordinates in Montgomery form). |
| 28 | +constexpr blst_p2 G2_GENERATOR_NEGATIVE{ |
| 29 | + {{{0xf5f28fa202940a10, 0xb3f5fb2687b4961a, 0xa1a893b53e2ae580, 0x9894999d1a3caee9, |
| 30 | + 0x6f67b7631863366b, 0x058191924350bcd7}, |
| 31 | + {0xa5a9c0759e23f606, 0xaaa0c59dbccd60c3, 0x3bb17e18e2867806, 0x1b1ab6cc8541b367, |
| 32 | + 0xc2b6ed0ef2158547, 0x11922a097360edf3}}}, |
| 33 | + {{{0x6d8bf5079fb65e61, 0xc52f05df531d63a5, 0x7f4a4d344ca692c9, 0xa887959b8577c95f, |
| 34 | + 0x4347fe40525c8734, 0x197d145bbaff0bb5}, |
| 35 | + {0x0c3e036d209afa4e, 0x0601d8f4863f9e23, 0xe0832636bacc0a84, 0xeb2def362a476f84, |
| 36 | + 0x64044f659f0ee1e9, 0x0ed54f48d5a1caa7}}}, |
| 37 | + {{ONE, {}}}}; |
| 38 | + |
| 39 | +/// The point from the G2 series, index 1 of the Ethereum KZG trusted setup, |
| 40 | +/// i.e. [s]₂ where s is the trusted setup's secret. |
| 41 | +/// Affine coordinates in Montgomery form. |
| 42 | +/// The original value in compressed form (y-parity bit and Fp2 x coordinate) |
| 43 | +/// is the ["g2_monomial"][1] of the JSON object found at |
| 44 | +/// https://github.com/ethereum/consensus-specs/blob/dev/presets/mainnet/trusted_setups/trusted_setup_4096.json#L8200 |
| 45 | +constexpr blst_p2_affine KZG_SETUP_G2_1{ |
| 46 | + {{{0x6120a2099b0379f9, 0xa2df815cb8210e4e, 0xcb57be5577bd3d4f, 0x62da0ea89a0c93f8, |
| 47 | + 0x02e0ee16968e150d, 0x171f09aea833acd5}, |
| 48 | + {0x11a3670749dfd455, 0x04991d7b3abffadc, 0x85446a8e14437f41, 0x27174e7b4e76e3f2, |
| 49 | + 0x7bfa6dd397f60a20, 0x02fcc329ac07080f}}}, |
| 50 | + {{{0xaa130838793b2317, 0xe236dd220f891637, 0x6502782925760980, 0xd05c25f60557ec89, |
| 51 | + 0x6095767a44064474, 0x185693917080d405}, |
| 52 | + {0x549f9e175b03dc0a, 0x32c0c95a77106cfe, 0x64a74eae5705d080, 0x53deeaf56659ed9e, |
| 53 | + 0x09a1d368508afb93, 0x12cf3a4525b5e9bd}}}}; |
| 54 | + |
| 55 | +/// Load and validate an element from the group order field. |
| 56 | +std::optional<blst_scalar> validate_scalar(std::span<const std::byte, 32> b) noexcept |
| 57 | +{ |
| 58 | + blst_scalar v; |
| 59 | + blst_scalar_from_bendian(&v, reinterpret_cast<const uint8_t*>(b.data())); |
| 60 | + return blst_scalar_fr_check(&v) ? std::optional{v} : std::nullopt; |
| 61 | +} |
| 62 | + |
| 63 | +/// Uncompress and validate a point from G1 subgroup. |
| 64 | +std::optional<blst_p1_affine> validate_G1(std::span<const std::byte, 48> b) noexcept |
| 65 | +{ |
| 66 | + blst_p1_affine r; |
| 67 | + if (blst_p1_uncompress(&r, reinterpret_cast<const uint8_t*>(b.data())) != BLST_SUCCESS) |
| 68 | + return std::nullopt; |
| 69 | + |
| 70 | + // Subgroup check is required by the spec but there are no test vectors |
| 71 | + // with points outside G1 which would satisfy the final pairings check. |
| 72 | + if (!blst_p1_affine_in_g1(&r)) |
| 73 | + return std::nullopt; |
| 74 | + return r; |
| 75 | +} |
| 76 | + |
| 77 | +/// Add two points from E1 and convert the result to affine form. |
| 78 | +/// The conversion to affine is very costly so use only if the affine of the result is needed. |
| 79 | +blst_p1_affine add_or_double(const blst_p1_affine& p, const blst_p1& q) noexcept |
| 80 | +{ |
| 81 | + blst_p1 r; |
| 82 | + blst_p1_add_or_double_affine(&r, &q, &p); |
| 83 | + blst_p1_affine ra; |
| 84 | + blst_p1_to_affine(&ra, &r); |
| 85 | + return ra; |
| 86 | +} |
| 87 | + |
| 88 | +blst_p1 mult(const blst_p1& p, const blst_scalar& v) noexcept |
| 89 | +{ |
| 90 | + blst_p1 r; |
| 91 | + blst_p1_mult(&r, &p, v.b, BLS_MODULUS_BITS); |
| 92 | + return r; |
| 93 | +} |
| 94 | + |
| 95 | +/// Add two points from E2 and convert the result to affine form. |
| 96 | +/// The conversion to affine is very costly so use only if the affine of the result is needed. |
| 97 | +blst_p2_affine add_or_double(const blst_p2_affine& p, const blst_p2& q) noexcept |
| 98 | +{ |
| 99 | + blst_p2 r; |
| 100 | + blst_p2_add_or_double_affine(&r, &q, &p); |
| 101 | + blst_p2_affine ra; |
| 102 | + blst_p2_to_affine(&ra, &r); |
| 103 | + return ra; |
| 104 | +} |
| 105 | + |
| 106 | +blst_p2 mult(const blst_p2& p, const blst_scalar& v) noexcept |
| 107 | +{ |
| 108 | + blst_p2 r; |
| 109 | + blst_p2_mult(&r, &p, v.b, BLS_MODULUS_BITS); |
| 110 | + return r; |
| 111 | +} |
| 112 | + |
| 113 | +bool pairings_verify( |
| 114 | + const blst_p1_affine& a1, const blst_p1_affine& b1, const blst_p2_affine& b2) noexcept |
| 115 | +{ |
| 116 | + blst_fp12 left; |
| 117 | + blst_aggregated_in_g1(&left, &a1); |
| 118 | + blst_fp12 right; |
| 119 | + blst_miller_loop(&right, &b2, &b1); |
| 120 | + return blst_fp12_finalverify(&left, &right); |
| 121 | +} |
| 122 | +} // namespace |
| 123 | + |
| 124 | +bool kzg_verify_proof(const std::byte versioned_hash[VERSIONED_HASH_SIZE], const std::byte z[32], |
| 125 | + const std::byte y[32], const std::byte commitment[48], const std::byte proof[48]) noexcept |
| 126 | +{ |
| 127 | + std::byte computed_versioned_hash[32]; |
| 128 | + sha256(computed_versioned_hash, commitment, 48); |
| 129 | + computed_versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; |
| 130 | + if (!std::ranges::equal(std::span{versioned_hash, 32}, computed_versioned_hash)) |
| 131 | + return false; |
| 132 | + |
| 133 | + // Load and validate scalars z and y. |
| 134 | + // TODO(C++26): The span construction can be done as std::snap(z, std::c_<32>). |
| 135 | + const auto zz = validate_scalar(std::span<const std::byte, 32>{z, 32}); |
| 136 | + if (!zz) |
| 137 | + return false; |
| 138 | + const auto yy = validate_scalar(std::span<const std::byte, 32>{y, 32}); |
| 139 | + if (!yy) |
| 140 | + return false; |
| 141 | + |
| 142 | + // Uncompress and validate the points C (representing the polynomial commitment) |
| 143 | + // and Pi (representing the proof). They both are valid to be points at infinity |
| 144 | + // when they prove a commitment to a constant polynomial, |
| 145 | + // see https://hackmd.io/@kevaundray/kzg-is-zero-proof-sound |
| 146 | + const auto C = validate_G1(std::span<const std::byte, 48>{commitment, 48}); |
| 147 | + if (!C) |
| 148 | + return false; |
| 149 | + const auto Pi = validate_G1(std::span<const std::byte, 48>{proof, 48}); |
| 150 | + if (!Pi) |
| 151 | + return false; |
| 152 | + |
| 153 | + // Compute -Y as [y * -1]₁. |
| 154 | + const auto neg_Y = mult(G1_GENERATOR_NEGATIVE, *yy); |
| 155 | + |
| 156 | + // Compute C - Y. It can happen that C == -Y so doubling may be needed. |
| 157 | + const auto C_sub_Y = add_or_double(*C, neg_Y); |
| 158 | + |
| 159 | + // Compute -Z as [z * -1]₂. |
| 160 | + const auto neg_Z = mult(G2_GENERATOR_NEGATIVE, *zz); |
| 161 | + |
| 162 | + // Compute X - Z which is [s - z]₂. |
| 163 | + const auto X_sub_Z = add_or_double(KZG_SETUP_G2_1, neg_Z); |
| 164 | + |
| 165 | + // e(C - [y]₁, [1]₂) =? e(Pi, [s - z]₂) |
| 166 | + return pairings_verify(C_sub_Y, *Pi, X_sub_Z); |
| 167 | +} |
| 168 | +} // namespace evmone::crypto |
0 commit comments