From 87ab964f12b0d7aecd7845656cef98d1676e8eaf Mon Sep 17 00:00:00 2001 From: George Kadianakis Date: Tue, 10 May 2022 17:27:57 +0300 Subject: [PATCH] Introduce high-level logic of new efficient transaction validation To validate a 4844 transaction in the mempool, the verifier checks that each provided KZG commitment matches the polynomial represented by the corresponding blob data. | d_1 | d_2 | d_3 | ... | d_4096 | -> commitment Before this patch, to do this validation, we reconstructed the commitment from the blob data (d_i above), and checked it against the provided commitment. This was expensive because computing a commitment from blob data (even using Lagrange basis) involves N scalar multiplications, where N is the number of field elements per blob. Initial benchmarking showed that this was about 40ms for N=4096 which was deemed too expensive. For more details see: https://hackmd.io/@protolambda/eip-4844-implementer-notes#Optimizations https://github.com/protolambda/go-ethereum/pull/4 In this patch, we speed this up by providing a KZG proof for each commitment. The verifier can check that proof to ensure that the KZG commitment matches the polynomial represented by the corresponding blob data. | d_1 | d_2 | d_3 | ... | d_4096 | -> commitment, proof To do so, we evaluate the blob data polynomial at a random point `x` to get a value `y`. We then use the KZG proof to ensure that the commited polynomial (i.e. the commitment) also evaluates to `y` at `x`. If the check passes, it means that the KZG commitment matches the polynomial represented by the blob data. This is significantly faster since evaluating the blob data polynomial at a random point using the Barycentric formula can be done efficiently with only field operations (see https://hackmd.io/@vbuterin/barycentric_evaluation). Then, verifying a KZG proof takes two pairing operations (which take about 0.6ms each). This brings the total verification cost to about 2 ms per blob. With some additional optimizations (using linear combination tricks as the ones linked above) we can batch all the blobs together into a single efficient verification, and hence verify the entire transaction in 2.5 ms. The same techniques can be used to efficiently verify blocks on the consensus side. --- EIPS/eip-4844.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-4844.md b/EIPS/eip-4844.md index 9fac2b8fe16dee..5593232713e4b4 100644 --- a/EIPS/eip-4844.md +++ b/EIPS/eip-4844.md @@ -344,6 +344,8 @@ class BlobTransactionNetworkWrapper(Container): tx: SignedBlobTransaction # KZGCommitment = Bytes48 blob_kzgs: List[KZGCommitment, MAX_TX_WRAP_KZG_COMMITMENTS] + # KZGProofs = Bytes48 + blob_proofs: List[KZGProof, MAX_TX_WRAP_KZG_COMMITMENTS] # BLSFieldElement = uint256 blobs: List[Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB], LIMIT_BLOBS_PER_TX] ``` @@ -355,10 +357,19 @@ def validate_blob_transaction_wrapper(wrapper: BlobTransactionNetworkWrapper): versioned_hashes = wrapper.tx.message.blob_versioned_hashes commitments = wrapper.blob_kzgs blobs = wrapper.blobs - assert len(versioned_hashes) == len(commitments) == len(blobs) - for versioned_hash, commitment, blob in zip(versioned_hashes, commitments, blobs): + proofs = wrapper.blob_proofs + + assert len(versioned_hashes) == len(commitments) == len(blobs) == len(proofs) + for versioned_hash, commitment, blob, proof in zip(versioned_hashes, commitments, blobs, proofs): # note: assert blob is not malformatted - assert commitment == blob_to_kzg(blob) + + # Get `x` point using Fiat-Shamir + x = hash_to_bls_field(commitment) + # Evaluate blob polynomial at `x` + y = evaluate_polynomial_in_evaluation_form(blob, x) + # Check that blob polynomial matches the `commitment` by verifying provided proof + assert verify_kzg_proof(commitment, x, y, proof) + # Finally check that the `versioned_hash` matches the `commitment` assert versioned_hash == kzg_to_versioned_hash(commitment) ```