From 8e3a912b283424c3ffbfe93619407e2167db601e 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 --- ethereum/core/build.gradle | 1 + .../mainnet/MainnetTransactionValidator.java | 120 ++++++++++++++++++ .../transaction/TransactionInvalidReason.java | 1 + gradle/versions.gradle | 2 +- 4 files changed, 123 insertions(+), 1 deletion(-) diff --git a/ethereum/core/build.gradle b/ethereum/core/build.gradle index 288a2e1f09..7683ba5b36 100644 --- a/ethereum/core/build.gradle +++ b/ethereum/core/build.gradle @@ -78,6 +78,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 12efd424ee..68b445ae63 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 @@ -22,6 +22,7 @@ import org.hyperledger.besu.datatypes.Hash; import org.hyperledger.besu.datatypes.Wei; 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; @@ -29,9 +30,15 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; 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. * @@ -40,6 +47,10 @@ import java.util.Set; */ 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 FeeMarket feeMarket; @@ -118,6 +129,14 @@ public class MainnetTransactionValidator { if (!signatureResult.isValid()) { return signatureResult; } + if (transaction.getType().equals(TransactionType.BLOB) + && transaction.getBlobsWithCommitments().isPresent()) { + final ValidationResult blobsResult = + validateTransactionsBlobs(transaction); + if (!blobsResult.isValid()) { + return blobsResult; + } + } if (goQuorumCompatibilityMode && transaction.hasCostParams()) { return ValidationResult.invalid( @@ -285,6 +304,107 @@ public class MainnetTransactionValidator { 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 d4297b7fa3..bc355b19ee 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 @@ -54,5 +54,6 @@ public enum TransactionInvalidReason { ETHER_VALUE_NOT_SUPPORTED, UPFRONT_FEE_TOO_HIGH, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER, + INVALID_BLOBS, LOWER_NONCE_INVALID_TRANSACTION_EXISTS } diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 353830434d..55fce70396 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -153,7 +153,7 @@ dependencyManagement { } dependency 'org.fusesource.jansi:jansi:2.4.0' - dependency 'tech.pegasys:jc-kzg-4844:0.1.0' + dependency 'tech.pegasys:jc-kzg-4844:0.3.0' dependencySet(group: 'org.hyperledger.besu', version: '0.7.1') { entry 'arithmetic' entry 'ipa-multipoint'