Skip to content

Commit

Permalink
verfifying kzg proof (hyperledger#4994)
Browse files Browse the repository at this point in the history
Signed-off-by: Jiri Peinlich <jiri.peinlich@gmail.com>
(cherry picked from commit 8e3a912)
(cherry picked from commit c325682b0f8acadc411acd6daafb55f5941785a0)
  • Loading branch information
gezero authored and jflo committed May 9, 2023
1 parent 8187f93 commit 50a0faf
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 2 deletions.
1 change: 1 addition & 0 deletions ethereum/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,23 @@
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;
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.
*
Expand All @@ -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;
Expand Down Expand Up @@ -119,6 +130,14 @@ public ValidationResult<TransactionInvalidReason> validate(
if (!signatureResult.isValid()) {
return signatureResult;
}
if (transaction.getType().equals(TransactionType.BLOB)
&& transaction.getBlobsWithCommitments().isPresent()) {
final ValidationResult<TransactionInvalidReason> blobsResult =
validateTransactionsBlobs(transaction);
if (!blobsResult.isValid()) {
return blobsResult;
}
}

final TransactionType transactionType = transaction.getType();
if (!acceptedTransactionTypes.contains(transactionType)) {
Expand Down Expand Up @@ -299,6 +318,107 @@ public ValidationResult<TransactionInvalidReason> validateTransactionSignature(
return ValidationResult.valid();
}

public ValidationResult<TransactionInvalidReason> 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<TransactionNetworkPayload.KZGCommitment> 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<Hash> 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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}

0 comments on commit 50a0faf

Please sign in to comment.