diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/SpecLogicEip4844.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/SpecLogicEip4844.java index f441e0b44ed..1626708a938 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/SpecLogicEip4844.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/SpecLogicEip4844.java @@ -31,11 +31,11 @@ import tech.pegasys.teku.spec.logic.versions.altair.util.AttestationUtilAltair; import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.BeaconStateMutatorsBellatrix; import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.BellatrixTransitionHelpers; -import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.MiscHelpersBellatrix; import tech.pegasys.teku.spec.logic.versions.bellatrix.statetransition.epoch.EpochProcessorBellatrix; import tech.pegasys.teku.spec.logic.versions.bellatrix.util.BlindBlockUtilBellatrix; import tech.pegasys.teku.spec.logic.versions.capella.block.BlockProcessorCapella; import tech.pegasys.teku.spec.logic.versions.eip4844.forktransition.Eip4844StateUpgrade; +import tech.pegasys.teku.spec.logic.versions.eip4844.helpers.MiscHelpersEip4844; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip4844; public class SpecLogicEip4844 extends AbstractSpecLogic { @@ -43,7 +43,7 @@ public class SpecLogicEip4844 extends AbstractSpecLogic { private SpecLogicEip4844( final Predicates predicates, - final MiscHelpersBellatrix miscHelpers, + final MiscHelpersEip4844 miscHelpers, final BeaconStateAccessorsAltair beaconStateAccessors, final BeaconStateMutatorsBellatrix beaconStateMutators, final OperationSignatureVerifier operationSignatureVerifier, @@ -83,7 +83,7 @@ public static SpecLogicEip4844 create( final SpecConfigEip4844 config, final SchemaDefinitionsEip4844 schemaDefinitions) { // Helpers final Predicates predicates = new Predicates(config); - final MiscHelpersBellatrix miscHelpers = new MiscHelpersBellatrix(config); + final MiscHelpersEip4844 miscHelpers = new MiscHelpersEip4844(config); final BeaconStateAccessorsAltair beaconStateAccessors = new BeaconStateAccessorsAltair(config, predicates, miscHelpers); final BeaconStateMutatorsBellatrix beaconStateMutators = diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844.java new file mode 100644 index 00000000000..eb250bb22e1 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844.java @@ -0,0 +1,120 @@ +/* + * Copyright ConsenSys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.versions.eip4844.helpers; + +import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.spec.config.SpecConfigEip4844.BLOB_TX_TYPE; +import static tech.pegasys.teku.spec.config.SpecConfigEip4844.VERSIONED_HASH_VERSION_KZG; + +import com.google.common.annotations.VisibleForTesting; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt32; +import tech.pegasys.teku.infrastructure.crypto.Hash; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.datastructures.execution.Transaction; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip4844.BlobsSidecar; +import tech.pegasys.teku.spec.logic.versions.bellatrix.helpers.MiscHelpersBellatrix; +import tech.pegasys.teku.spec.logic.versions.eip4844.types.VersionedHash; +import tech.pegasys.teku.spec.logic.versions.eip4844.util.KZGUtilEip4844; + +public class MiscHelpersEip4844 extends MiscHelpersBellatrix { + + public MiscHelpersEip4844(final SpecConfig specConfig) { + super(specConfig); + } + + public void validateBlobSidecar( + final UInt64 slot, + final Bytes32 beaconBlockRoot, + final List kzgCommitments, + final BlobsSidecar blobsSidecar) { + checkArgument( + slot.equals(blobsSidecar.getBeaconBlockSlot()), + "Block slot should match blobsSidecar slot"); + checkArgument( + beaconBlockRoot.equals(blobsSidecar.getBeaconBlockRoot()), + "Block root should match blobsSidecar beacon block root"); + checkArgument( + kzgCommitments.size() == blobsSidecar.getBlobs().size(), + "Number of kzgCommitments should match number of blobs"); + KZGUtilEip4844.verifyAggregateKZGProof( + blobsSidecar.getBlobs(), kzgCommitments, blobsSidecar.getKZGAggregatedProof()); + } + + public boolean isDataAvailable( + final UInt64 slot, + final Bytes32 beaconBlockRoot, + final List kzgCommitments, + final BlobsSidecar blobsSidecar) { + validateBlobSidecar(slot, beaconBlockRoot, kzgCommitments, blobsSidecar); + return true; + } + + @VisibleForTesting + public VersionedHash kzgCommitmentToVersionedHash(final KZGCommitment kzgCommitment) { + return VersionedHash.create( + VERSIONED_HASH_VERSION_KZG, Hash.sha256(kzgCommitment.getBytesCompressed())); + } + + @VisibleForTesting + public List txPeekBlobVersionedHashes(final Transaction transaction) { + checkArgument(isBlobTransaction(transaction), "Transaction should be of BLOB type"); + final Bytes txData = transaction.getBytes(); + // 1st byte is transaction type, next goes ssz encoded SignedBlobTransaction + // Getting variable length BlobTransaction offset, which is the message of signed tx + final int messageOffset = + UInt32.fromBytes(txData.slice(1, 4), ByteOrder.LITTLE_ENDIAN).add(1).intValue(); + // Getting blobVersionedHashes field offset in BlobTransaction + // field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188 + final int blobVersionedHashesOffset = + messageOffset + + UInt32.fromBytes(txData.slice(messageOffset + 188, 4), ByteOrder.LITTLE_ENDIAN) + .intValue(); + final List versionedHashes = new ArrayList<>(); + for (int hashStartOffset = blobVersionedHashesOffset; + hashStartOffset < txData.size(); + hashStartOffset += VersionedHash.SIZE) { + versionedHashes.add( + new VersionedHash(Bytes32.wrap(txData.slice(hashStartOffset, VersionedHash.SIZE)))); + } + + return versionedHashes; + } + + private boolean isBlobTransaction(final Transaction transaction) { + return transaction.getBytes().get(0) == BLOB_TX_TYPE.get(0); + } + + public boolean verifyKZGCommitmentsAgainstTransactions( + final List transactions, final List kzgCommitments) { + final List transactionsVersionedHashes = + transactions.stream() + .filter(this::isBlobTransaction) + .map(this::txPeekBlobVersionedHashes) + .flatMap(List::stream) + .collect(Collectors.toList()); + final List commitmentsVersionedHashes = + kzgCommitments.stream() + .map(this::kzgCommitmentToVersionedHash) + .collect(Collectors.toList()); + return transactionsVersionedHashes.equals(commitmentsVersionedHashes); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/types/VersionedHash.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/types/VersionedHash.java new file mode 100644 index 00000000000..fe3ab9c0437 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/types/VersionedHash.java @@ -0,0 +1,64 @@ +/* + * Copyright ConsenSys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.versions.eip4844.types; + +import static com.google.common.base.Preconditions.checkArgument; + +import java.util.Objects; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class VersionedHash { + + public static final int SIZE = 32; + + final Bytes version; + final Bytes value; + + private VersionedHash(Bytes version, Bytes value) { + this.version = version; + this.value = value; + } + + public VersionedHash(final Bytes32 value) { + this.version = value.slice(0, 1); + this.value = value.slice(1); + } + + public static VersionedHash create(final Bytes version, final Bytes32 hash) { + checkArgument(version.size() == 1, "Version is 1-byte flag"); + return new VersionedHash(version, hash.slice(1)); + } + + public boolean isVersion(final Bytes version) { + return this.version.equals(version); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + VersionedHash that = (VersionedHash) o; + return Objects.equals(version, that.version) && Objects.equals(value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(version, value); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/util/KZGUtilEip4844.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/util/KZGUtilEip4844.java new file mode 100644 index 00000000000..805ef3c67af --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip4844/util/KZGUtilEip4844.java @@ -0,0 +1,29 @@ +/* + * Copyright ConsenSys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.versions.eip4844.util; + +import java.util.List; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.kzg.KZGProof; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip4844.Blob; + +public class KZGUtilEip4844 { + + public static void verifyAggregateKZGProof( + final List blobs, + final List expectedKZGCommitments, + final KZGProof kzgProof) { + // TODO: implement + } +} diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844Test.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844Test.java new file mode 100644 index 00000000000..ceaa9a314de --- /dev/null +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/MiscHelpersEip4844Test.java @@ -0,0 +1,76 @@ +/* + * Copyright ConsenSys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.versions.eip4844.helpers; + +import static org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.teku.spec.config.SpecConfigEip4844.BLOB_TX_TYPE; +import static tech.pegasys.teku.spec.config.SpecConfigEip4844.VERSIONED_HASH_VERSION_KZG; + +import com.google.common.io.ByteSource; +import com.google.common.io.Resources; +import java.io.IOException; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.execution.Transaction; +import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; +import tech.pegasys.teku.spec.logic.versions.eip4844.types.VersionedHash; + +class MiscHelpersEip4844Test { + + private static final String SIGNED_BLOB_TX_LOCATION = "signed_blob_transaction.ssz"; + private static final VersionedHash VERSIONED_HASH = + VersionedHash.create( + VERSIONED_HASH_VERSION_KZG, + Bytes32.fromHexString( + "0x391610cf24e7c540192b80ddcfea77b0d3912d94e922682f3b286eee041e6f76")); + + private final Spec spec = TestSpecFactory.createMinimalEip4844(); + private final MiscHelpersEip4844 miscHelpersEip4844 = + new MiscHelpersEip4844(spec.getGenesisSpecConfig()); + + @Test + public void versionedHash() { + final VersionedHash actual = + miscHelpersEip4844.kzgCommitmentToVersionedHash( + KZGCommitment.fromHexString( + "0x85d1edf1ee88f68260e750abb2c766398ad1125d4e94e1de04034075ccbd2bb79c5689b952ef15374fd03ca2b2475371")); + assertThat(actual).isEqualTo(VERSIONED_HASH); + } + + @Test + public void txPeekBlobVersionedHashes() throws IOException { + final Bytes sszTx = loadData(SIGNED_BLOB_TX_LOCATION); + assertThat(sszTx.get(0)).isEqualTo(BLOB_TX_TYPE.get(0)); + final TransactionSchema transactionSchema = + new TransactionSchema(spec.getGenesisSpecConfig().toVersionEip4844().orElseThrow()); + final Transaction blobTx = transactionSchema.sszDeserialize(sszTx); + List versionedHashes = miscHelpersEip4844.txPeekBlobVersionedHashes(blobTx); + assertThat(versionedHashes.size()).isEqualTo(5); + assertThat( + versionedHashes.stream().allMatch(hash -> hash.isVersion(VERSIONED_HASH_VERSION_KZG))) + .isTrue(); + assertThat(versionedHashes.stream().allMatch(hash -> hash.equals(VERSIONED_HASH))).isTrue(); + } + + private Bytes loadData(final String location) throws IOException { + final ByteSource binaryData = + Resources.asByteSource(Resources.getResource(MiscHelpersEip4844Test.class, location)); + return Bytes.wrap(binaryData.read()); + } +} diff --git a/ethereum/spec/src/test/resources/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/signed_blob_transaction.ssz b/ethereum/spec/src/test/resources/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/signed_blob_transaction.ssz new file mode 100644 index 00000000000..05a5575523d Binary files /dev/null and b/ethereum/spec/src/test/resources/tech/pegasys/teku/spec/logic/versions/eip4844/helpers/signed_blob_transaction.ssz differ