From 16bd1c4462a13ef3bee78210e3ad395e91a65f0e Mon Sep 17 00:00:00 2001 From: Jiri Peinlich Date: Tue, 24 Jan 2023 15:48:35 +0100 Subject: [PATCH] verfifying kzg proof (#4994) Signed-off-by: Jiri Peinlich (cherry picked from commit 8e3a912b283424c3ffbfe93619407e2167db601e) (cherry picked from commit c325682b0f8acadc411acd6daafb55f5941785a0) --- ethereum/core/build.gradle | 1 + .../mainnet/MainnetTransactionValidator.java | 120 ++++++++++++++++++ .../transaction/TransactionInvalidReason.java | 4 +- 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 4158c564b85..b6a10bb48b1 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -80,6 +80,7 @@ dependencies { implementation files('libs/tuweni-ssz-2.4.0-SNAPSHOT.jar') implementation 'org.hyperledger.besu:bls12-381' implementation 'org.immutables:value-annotations' + implementation 'tech.pegasys:jc-kzg-4844' implementation 'io.prometheus:simpleclient_guava' diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java index 549828ac906..a83027fb701 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java @@ -23,6 +23,7 @@ import org.hyperledger.besu.ethereum.GasLimitCalculator; import org.hyperledger.besu.ethereum.core.Transaction; import org.hyperledger.besu.ethereum.core.TransactionFilter; +import org.hyperledger.besu.ethereum.core.encoding.ssz.TransactionNetworkPayload; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; @@ -30,9 +31,15 @@ import org.hyperledger.besu.plugin.data.TransactionType; import java.math.BigInteger; +import java.util.List; import java.util.Optional; import java.util.Set; +import ethereum.ckzg4844.CKZG4844JNI; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.crypto.digests.SHA256Digest; + /** * Validates a transaction based on Frontier protocol runtime requirements. * @@ -41,6 +48,10 @@ */ public class MainnetTransactionValidator { + private final long MAX_DATA_GAS_PER_BLOCK = 524_288; // 2**19 + private final long DATA_GAS_PER_BLOB = 131_072; // 2**17 + private final byte BLOB_COMMITMENT_VERSION_KZG = 0x01; + private final GasCalculator gasCalculator; private final GasLimitCalculator gasLimitCalculator; private final FeeMarket feeMarket; @@ -119,6 +130,14 @@ public ValidationResult validate( if (!signatureResult.isValid()) { return signatureResult; } + if (transaction.getType().equals(TransactionType.BLOB) + && transaction.getBlobsWithCommitments().isPresent()) { + final ValidationResult blobsResult = + validateTransactionsBlobs(transaction); + if (!blobsResult.isValid()) { + return blobsResult; + } + } final TransactionType transactionType = transaction.getType(); if (!acceptedTransactionTypes.contains(transactionType)) { @@ -299,6 +318,107 @@ public ValidationResult validateTransactionSignature( return ValidationResult.valid(); } + public ValidationResult validateTransactionsBlobs( + final Transaction transaction) { + + if (transaction.getBlobsWithCommitments().isEmpty()) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction blobs are empty, cannot verify without blobs"); + } + + Transaction.BlobsWithCommitments blobsWithCommitments = + transaction.getBlobsWithCommitments().get(); + + if (blobsWithCommitments.blobs.getElements().size() + > MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "Too many transaction blobs (" + + blobsWithCommitments.blobs.getElements().size() + + ") in transaction, max is " + + MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB); + } + + if (blobsWithCommitments.blobs.getElements().size() + != blobsWithCommitments.kzgCommitments.getElements().size()) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction blobs and commitments are not the same size"); + } + + List commitments = + blobsWithCommitments.kzgCommitments.getElements(); + for (TransactionNetworkPayload.KZGCommitment commitment : commitments) { + if (commitment.getData().get(0) != BLOB_COMMITMENT_VERSION_KZG) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction blobs commitment version is not supported"); + } + } + if (transaction.getVersionedHashes().isEmpty()) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction versioned hashes are empty, cannot verify without versioned hashes"); + } + List versionedHashes = transaction.getVersionedHashes().get(); + + for (int i = 0; i < versionedHashes.size(); i++) { + TransactionNetworkPayload.KZGCommitment commitment = + blobsWithCommitments.kzgCommitments.getElements().get(i); + Hash versionedHash = versionedHashes.get(i); + Hash calculatedVersionedHash = hashCommitment(commitment); + if (!calculatedVersionedHash.equals(versionedHash)) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction blobs commitment hash does not match commitment"); + } + } + + Bytes blobs = + blobsWithCommitments.blobs.getElements().stream() + .map( + blob -> + blob.getElements().stream() + .map(sszuInt256Wrapper -> (Bytes) sszuInt256Wrapper.getData().toBytes()) + .reduce(Bytes::concatenate) + .orElseThrow()) + .reduce(Bytes::concatenate) + .orElseThrow(); + + Bytes kzgCommitments = + blobsWithCommitments.kzgCommitments.getElements().stream() + .map(commitment -> commitment.getData()) + .reduce(Bytes::concatenate) + .orElseThrow(); + + boolean kzgVerification = + CKZG4844JNI.verifyAggregateKzgProof( + blobs.toArrayUnsafe(), + kzgCommitments.toArrayUnsafe(), + blobsWithCommitments.blobs.getElements().size(), + blobsWithCommitments.kzgProof.getBytes().toArrayUnsafe()); + if (!kzgVerification) { + return ValidationResult.invalid( + TransactionInvalidReason.INVALID_BLOBS, + "transaction blobs kzg proof verification failed"); + } + + return ValidationResult.valid(); + } + + private Hash hashCommitment(final TransactionNetworkPayload.KZGCommitment commitment) { + SHA256Digest digest = new SHA256Digest(); + digest.update(commitment.getData().toArrayUnsafe(), 0, commitment.getData().size()); + + final byte[] dig = new byte[digest.getDigestSize()]; + + digest.doFinal(dig, 0); + + dig[0] = BLOB_COMMITMENT_VERSION_KZG; + return Hash.wrap(Bytes32.wrap(dig)); + } + private boolean isSenderAllowed( final Transaction transaction, final TransactionValidationParams validationParams) { if (validationParams.checkLocalPermissions() || validationParams.checkOnchainPermissions()) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java index 84487d6bfb4..af0782cc217 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/transaction/TransactionInvalidReason.java @@ -45,12 +45,12 @@ public enum TransactionInvalidReason { GAS_PRICE_BELOW_CURRENT_BASE_FEE, TX_FEECAP_EXCEEDED, INTERNAL_ERROR, - + INVALID_BLOBS, // Private Transaction Invalid Reasons PRIVATE_TRANSACTION_FAILED, PRIVATE_NONCE_TOO_LOW, OFFCHAIN_PRIVACY_GROUP_DOES_NOT_EXIST, PRIVATE_NONCE_TOO_HIGH, PRIVATE_VALUE_NOT_ZERO, - PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE + PRIVATE_UNIMPLEMENTED_TRANSACTION_TYPE, }