diff --git a/CHANGELOG.md b/CHANGELOG.md index c6cbb7789..6e61a7db2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Upcoming Release ### Features Added - Azure Key Vault connections are now cached per credential/vault set instead of rebuilt on every key load, reducing bulk-load time. [#1222][PR_1222] +- Updated Glamsterdam (ePBS) signing support to a fork-versioned request format, matching [remote-signing-api#28](https://github.com/ethereum/remote-signing-api/pull/28). ### Bugs Fixed - Azure Key Vault SECP256K1 signing now uses one official Azure SDK `CryptographyClient` per key instead of REST workaround. [#1222][PR_1222] @@ -29,6 +30,11 @@ - Docker images are unchanged — they have shipped Java 25 since 25.12.0. - Contributors no longer need to install JDK 25 manually. The build now uses a Gradle toolchain (`JavaLanguageVersion.of(25)`) with the foojay resolver, so Gradle will auto-detect a locally installed JDK 25 and download Temurin 25 if none is found. The Gradle daemon itself can run on any JDK supported by Gradle 9 (17+). +### Features Added +- Initial signing support for the upcoming Glamsterdam (GLOAS / ePBS) fork. Subject to change until the next Teku release pins the schemas. PR [#1192][PR_1192]. + +[PR_1192]: https://github.com/Consensys/web3signer/pull/1192 + ### Security - Update base docker image to latest LTS Ubuntu 26.04. diff --git a/acceptance-tests/build.gradle b/acceptance-tests/build.gradle index 64afc40fe..e019d468d 100644 --- a/acceptance-tests/build.gradle +++ b/acceptance-tests/build.gradle @@ -47,6 +47,7 @@ dependencies { testImplementation 'tech.pegasys.teku.internal:serializer' testImplementation 'tech.pegasys.teku.internal:unsigned' testImplementation 'tech.pegasys.teku.internal:async' + testImplementation 'tech.pegasys.teku.internal:execution-types' testImplementation 'io.rest-assured:rest-assured' testImplementation 'org.web3j:core' testImplementation 'org.web3j:crypto' diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfiguration.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfiguration.java index 4e44a534a..3ee5ba209 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfiguration.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfiguration.java @@ -72,6 +72,7 @@ public class SignerConfiguration { private final Optional denebForkEpoch; private final Optional electraForkEpoch; private final Optional fuluForkEpoch; + private final Optional gloasForkEpoch; private final Optional network; private final boolean keyManagerApiEnabled; private Optional watermarkRepairParameters; @@ -123,6 +124,7 @@ public SignerConfiguration( final Optional denebForkEpoch, final Optional electraForkEpoch, final Optional fuluForkEpoch, + final Optional gloasForkEpoch, final Optional network, final boolean keyManagerApiEnabled, final Optional watermarkRepairParameters, @@ -170,6 +172,7 @@ public SignerConfiguration( this.denebForkEpoch = denebForkEpoch; this.electraForkEpoch = electraForkEpoch; this.fuluForkEpoch = fuluForkEpoch; + this.gloasForkEpoch = gloasForkEpoch; this.network = network; this.keyManagerApiEnabled = keyManagerApiEnabled; this.watermarkRepairParameters = watermarkRepairParameters; @@ -333,6 +336,10 @@ public Optional getFuluForkEpoch() { return fuluForkEpoch; } + public Optional getGloasForkEpoch() { + return gloasForkEpoch; + } + public Optional getNetwork() { return network; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfigurationBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfigurationBuilder.java index c5a0cbb2a..d67f3d678 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfigurationBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/SignerConfigurationBuilder.java @@ -74,6 +74,7 @@ public class SignerConfigurationBuilder { private Long denebForkEpoch = null; private Long electraForkEpoch = null; private Long fuluForkEpoch = null; + private Long gloasForkEpoch = null; private String network = null; private boolean keyManagerApiEnabled = false; private KeystoresParameters keystoresParameters; @@ -280,6 +281,11 @@ public SignerConfigurationBuilder withFuluForkEpoch(final long fuluForkEpoch) { return this; } + public SignerConfigurationBuilder withGloasForkEpoch(final long gloasForkEpoch) { + this.gloasForkEpoch = gloasForkEpoch; + return this; + } + public SignerConfigurationBuilder withNetwork(final String network) { this.network = network; return this; @@ -382,6 +388,7 @@ public SignerConfiguration build() { Optional.ofNullable(denebForkEpoch), Optional.ofNullable(electraForkEpoch), Optional.ofNullable(fuluForkEpoch), + Optional.ofNullable(gloasForkEpoch), Optional.ofNullable(network), keyManagerApiEnabled, Optional.ofNullable(watermarkRepairParameters), diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java index a1e65e8ae..ebc84eaa2 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsConfigFileImpl.java @@ -450,6 +450,10 @@ private Map createEth2SlashingProtectionArgs() { yamlConfigMap.put("eth2.Xnetwork-fulu-fork-epoch", signerConfig.getFuluForkEpoch().get()); } + if (signerConfig.getGloasForkEpoch().isPresent()) { + yamlConfigMap.put("eth2.Xnetwork-gloas-fork-epoch", signerConfig.getGloasForkEpoch().get()); + } + if (signerConfig.getNetwork().isPresent()) { yamlConfigMap.put("eth2.network", signerConfig.getNetwork().get()); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java index e7a28e938..b8745077c 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/runner/CmdLineParamsDefaultImpl.java @@ -322,6 +322,11 @@ private Collection createEth2Args() { params.add(Long.toString(signerConfig.getFuluForkEpoch().get())); } + if (signerConfig.getGloasForkEpoch().isPresent()) { + params.add("--Xnetwork-gloas-fork-epoch"); + params.add(Long.toString(signerConfig.getGloasForkEpoch().get())); + } + if (signerConfig.getNetwork().isPresent()) { params.add("--network"); params.add(signerConfig.getNetwork().get()); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2RequestUtils.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2RequestUtils.java index 901242286..1a4857240 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2RequestUtils.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2RequestUtils.java @@ -16,6 +16,7 @@ import static tech.pegasys.web3signer.core.util.DepositSigningRootUtil.computeDomain; import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; @@ -37,6 +38,7 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.RandaoReveal; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncCommitteeMessage; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ValidatorRegistration; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.VersionedRequest; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AggregateAndProof; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Attestation; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData; @@ -47,7 +49,15 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Checkpoint; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Eth1Data; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Fork; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.VoluntaryExit; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.electra.ExecutionRequests; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadGloas; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences; import tech.pegasys.web3signer.core.util.DepositSigningRootUtil; import java.util.Random; @@ -88,6 +98,12 @@ public class Eth2RequestUtils { private static final Eth2BlockSigningRequestUtil ALTAIR_BLOCK_UTIL = new Eth2BlockSigningRequestUtil(SpecMilestone.ALTAIR); + // Gloas Spec + private static final Spec GLOAS_SPEC = TestSpecFactory.createMinimalGloas(); + private static final DataStructureUtil GLOAS_DATA_STRUCTURE_UTIL = + new DataStructureUtil(GLOAS_SPEC); + private static final SigningRootUtil GLOAS_SIGNING_ROOT_UTIL = new SigningRootUtil(GLOAS_SPEC); + public static Eth2SigningRequestBody createCannedRequest(final ArtifactType artifactType) { return switch (artifactType) { case DEPOSIT -> createDepositRequest(); @@ -116,6 +132,16 @@ public static Eth2SigningRequestBody createCannedRequest(final ArtifactType arti createSyncCommitteeContributionAndProofRequest(); case VALIDATOR_REGISTRATION -> createValidatorRegistrationRequest(); + + case EXECUTION_PAYLOAD_BID -> createExecutionPayloadBidRequest(); + + case EXECUTION_PAYLOAD_ENVELOPE -> createExecutionPayloadEnvelopeRequest(); + + case PAYLOAD_ATTESTATION_MESSAGE -> createPayloadAttestationMessageRequest(); + + case PROPOSER_PREFERENCES -> createProposerPreferencesRequest(); + + case BUILDER_REQUEST_AUTH -> createBuilderRequestAuthRequest(); }; } @@ -416,6 +442,129 @@ private static Eth2SigningRequestBody createValidatorRegistrationRequest() { .build(); } + public static ForkInfo gloasForkInfo() { + final tech.pegasys.teku.spec.datastructures.state.Fork internalFork = + GLOAS_SPEC.getForkSchedule().getFork(UInt64.ZERO); + final Fork fork = + new Fork( + internalFork.getPreviousVersion(), + internalFork.getCurrentVersion(), + internalFork.getEpoch()); + return new ForkInfo(fork, Bytes32.fromHexString(GENESIS_VALIDATORS_ROOT)); + } + + private static Eth2SigningRequestBody createExecutionPayloadBidRequest() { + final ForkInfo forkInfo = gloasForkInfo(); + final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid randomBid = + GLOAS_DATA_STRUCTURE_UTIL.randomExecutionPayloadBid(); + final ExecutionPayloadBid bid = + new ExecutionPayloadBid( + randomBid.getParentBlockHash(), + randomBid.getParentBlockRoot(), + randomBid.getBlockHash(), + randomBid.getPrevRandao(), + new Bytes20(randomBid.getFeeRecipient().getWrappedBytes()), + randomBid.getGasLimit(), + randomBid.getBuilderIndex(), + randomBid.getSlot(), + randomBid.getValue(), + randomBid.getExecutionPayment(), + randomBid.getBlobKzgCommitments().stream() + .map(c -> new KZGCommitment(c.getKZGCommitment())) + .toList(), + randomBid.getExecutionRequestsRoot()); + final Bytes signingRoot = + GLOAS_SIGNING_ROOT_UTIL.signingRootForSignExecutionPayloadBid( + randomBid, forkInfo.asInternalForkInfo()); + return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() + .withType(ArtifactType.EXECUTION_PAYLOAD_BID) + .withSigningRoot(signingRoot) + .withForkInfo(forkInfo) + .withExecutionPayloadBid(new VersionedRequest<>(SpecMilestone.GLOAS, bid)) + .build(); + } + + private static Eth2SigningRequestBody createExecutionPayloadEnvelopeRequest() { + final ForkInfo forkInfo = gloasForkInfo(); + final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope + randomEnvelope = + GLOAS_DATA_STRUCTURE_UTIL.randomExecutionPayloadEnvelope(UInt64.valueOf(7)); + final ExecutionPayloadEnvelope envelope = + new ExecutionPayloadEnvelope( + new ExecutionPayloadGloas(randomEnvelope.getPayload()), + new ExecutionRequests(randomEnvelope.getExecutionRequests()), + randomEnvelope.getBuilderIndex(), + randomEnvelope.getBeaconBlockRoot(), + randomEnvelope.getParentBeaconBlockRoot()); + final Bytes signingRoot = + GLOAS_SIGNING_ROOT_UTIL.signingRootForSignExecutionPayloadEnvelope( + randomEnvelope, forkInfo.asInternalForkInfo()); + return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() + .withType(ArtifactType.EXECUTION_PAYLOAD_ENVELOPE) + .withSigningRoot(signingRoot) + .withForkInfo(forkInfo) + .withExecutionPayloadEnvelope(new VersionedRequest<>(SpecMilestone.GLOAS, envelope)) + .build(); + } + + private static Eth2SigningRequestBody createPayloadAttestationMessageRequest() { + final ForkInfo forkInfo = gloasForkInfo(); + final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData + randomData = GLOAS_DATA_STRUCTURE_UTIL.randomPayloadAttestationData(UInt64.valueOf(7)); + final PayloadAttestationData payloadAttestationData = + new PayloadAttestationData( + randomData.getBeaconBlockRoot(), + randomData.getSlot(), + randomData.isPayloadPresent(), + randomData.isBlobDataAvailable()); + final Bytes signingRoot = + GLOAS_SIGNING_ROOT_UTIL.signingRootForSignPayloadAttestationData( + randomData, forkInfo.asInternalForkInfo()); + return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() + .withType(ArtifactType.PAYLOAD_ATTESTATION_MESSAGE) + .withSigningRoot(signingRoot) + .withForkInfo(forkInfo) + .withPayloadAttestationMessage( + new VersionedRequest<>(SpecMilestone.GLOAS, payloadAttestationData)) + .build(); + } + + private static Eth2SigningRequestBody createProposerPreferencesRequest() { + final ForkInfo forkInfo = gloasForkInfo(); + final tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ProposerPreferences + randomPreferences = GLOAS_DATA_STRUCTURE_UTIL.randomProposerPreferences(); + final ProposerPreferences proposerPreferences = + new ProposerPreferences( + randomPreferences.getDependentRoot(), + randomPreferences.getProposalSlot(), + randomPreferences.getValidatorIndex(), + randomPreferences.getFeeRecipient(), + randomPreferences.getTargetGasLimit()); + final Bytes signingRoot = + GLOAS_SIGNING_ROOT_UTIL.signingRootForSignProposerPreferences( + randomPreferences, forkInfo.asInternalForkInfo()); + return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() + .withType(ArtifactType.PROPOSER_PREFERENCES) + .withSigningRoot(signingRoot) + .withForkInfo(forkInfo) + .withProposerPreferences(new VersionedRequest<>(SpecMilestone.GLOAS, proposerPreferences)) + .build(); + } + + private static Eth2SigningRequestBody createBuilderRequestAuthRequest() { + final tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderRequestAuth + randomRequestAuth = GLOAS_DATA_STRUCTURE_UTIL.randomBuilderRequestAuth(); + final BuilderRequestAuth builderRequestAuth = + new BuilderRequestAuth(randomRequestAuth.getData().getBytes(), randomRequestAuth.getSlot()); + final Bytes signingRoot = + GLOAS_SIGNING_ROOT_UTIL.signingRootForSignBuilderRequestAuth(randomRequestAuth); + return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() + .withType(ArtifactType.BUILDER_REQUEST_AUTH) + .withSigningRoot(signingRoot) + .withBuilderRequestAuth(new VersionedRequest<>(SpecMilestone.GLOAS, builderRequestAuth)) + .build(); + } + private static tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair .ContributionAndProof getContributionAndProof() { diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2SigningRequestBodyBuilder.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2SigningRequestBodyBuilder.java index 8fbdd2ab9..41dde8af0 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2SigningRequestBodyBuilder.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2SigningRequestBodyBuilder.java @@ -23,10 +23,16 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncAggregatorSelectionData; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.SyncCommitteeMessage; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ValidatorRegistration; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.VersionedRequest; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BeaconBlock; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.VoluntaryExit; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.ContributionAndProof; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences; import org.apache.tuweni.bytes.Bytes; @@ -46,6 +52,11 @@ public final class Eth2SigningRequestBodyBuilder { private SyncAggregatorSelectionData syncAggregatorSelectionData; private ContributionAndProof contributionAndProof; private ValidatorRegistration validatorRegistration; + private VersionedRequest executionPayloadBid; + private VersionedRequest executionPayloadEnvelope; + private VersionedRequest payloadAttestationMessage; + private VersionedRequest proposerPreferences; + private VersionedRequest builderRequestAuth; private Eth2SigningRequestBodyBuilder() {} @@ -133,6 +144,36 @@ public Eth2SigningRequestBodyBuilder withValidatorRegistration( return this; } + public Eth2SigningRequestBodyBuilder withExecutionPayloadBid( + VersionedRequest executionPayloadBid) { + this.executionPayloadBid = executionPayloadBid; + return this; + } + + public Eth2SigningRequestBodyBuilder withExecutionPayloadEnvelope( + VersionedRequest executionPayloadEnvelope) { + this.executionPayloadEnvelope = executionPayloadEnvelope; + return this; + } + + public Eth2SigningRequestBodyBuilder withPayloadAttestationMessage( + VersionedRequest payloadAttestationMessage) { + this.payloadAttestationMessage = payloadAttestationMessage; + return this; + } + + public Eth2SigningRequestBodyBuilder withProposerPreferences( + VersionedRequest proposerPreferences) { + this.proposerPreferences = proposerPreferences; + return this; + } + + public Eth2SigningRequestBodyBuilder withBuilderRequestAuth( + VersionedRequest builderRequestAuth) { + this.builderRequestAuth = builderRequestAuth; + return this; + } + public Eth2SigningRequestBody build() { return new Eth2SigningRequestBody( type, @@ -149,6 +190,11 @@ public Eth2SigningRequestBody build() { syncCommitteeMessage, syncAggregatorSelectionData, contributionAndProof, - validatorRegistration); + validatorRegistration, + executionPayloadBid, + executionPayloadEnvelope, + payloadAttestationMessage, + proposerPreferences, + builderRequestAuth); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/BlsSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/BlsSigningAcceptanceTest.java index 47de7c2b0..a1b74f2be 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/BlsSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/BlsSigningAcceptanceTest.java @@ -305,7 +305,12 @@ public void failsIfSigningRootDoesNotMatchSigningData(final ArtifactType artifac request.syncCommitteeMessage(), request.syncAggregatorSelectionData(), request.contributionAndProof(), - request.validatorRegistration()); + request.validatorRegistration(), + request.executionPayloadBid(), + request.executionPayloadEnvelope(), + request.payloadAttestationMessage(), + request.proposerPreferences(), + request.builderRequestAuth()); final Response response = signer.eth2Sign(KEY_PAIR.getPublicKey().toString(), requestWithMismatchedSigningRoot); @@ -343,7 +348,12 @@ public void ableToSignWithoutSigningRootField(final ContentType acceptableConten request.syncCommitteeMessage(), request.syncAggregatorSelectionData(), request.contributionAndProof(), - request.validatorRegistration()); + request.validatorRegistration(), + request.executionPayloadBid(), + request.executionPayloadEnvelope(), + request.payloadAttestationMessage(), + request.proposerPreferences(), + request.builderRequestAuth()); final Response response = signer.eth2Sign( @@ -400,6 +410,12 @@ private void setupMinimalWeb3Signer(final ArtifactType artifactType) { SYNC_COMMITTEE_SELECTION_PROOF, SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF -> setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.ALTAIR); + case EXECUTION_PAYLOAD_BID, + EXECUTION_PAYLOAD_ENVELOPE, + PAYLOAD_ATTESTATION_MESSAGE, + PROPOSER_PREFERENCES, + BUILDER_REQUEST_AUTH -> + setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.GLOAS); default -> setupEth2Signer(Eth2Network.MINIMAL, SpecMilestone.PHASE0); } } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AggregateAndProofSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AggregateAndProofSigningAcceptanceTest.java index 1e6c64d19..6160b86bd 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AggregateAndProofSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AggregateAndProofSigningAcceptanceTest.java @@ -57,7 +57,7 @@ void setup() { @ParameterizedTest(name = "#{index} - {0}: Sign and verify AggregateAndProofV2 Signature") @EnumSource( value = SpecMilestone.class, - names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA", "FULU"}) + names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA", "FULU", "GLOAS"}) void signAndVerifyAggregateAndProofV2Signature(final SpecMilestone specMilestone) throws Exception { final Eth2AggregateAndProofSigningRequestUtil util = diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2BlockSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2BlockSigningAcceptanceTest.java index 899d326ba..2da2857bc 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2BlockSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2BlockSigningAcceptanceTest.java @@ -59,7 +59,7 @@ void setup() { @ParameterizedTest(name = "#{index} - Sign and verify BlockV2 Signature for spec {0}") @EnumSource( value = SpecMilestone.class, - names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA", "FULU"}) + names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA", "FULU", "GLOAS"}) void signAndVerifyBlockV2Signature(final SpecMilestone specMilestone) throws Exception { final Eth2BlockSigningRequestUtil util = new Eth2BlockSigningRequestUtil(specMilestone); @@ -90,7 +90,7 @@ void signAndVerifyLegacyBlockSignature() throws Exception { name = "#{index} - Empty block request for spec {0} should return bad request status") @EnumSource( value = SpecMilestone.class, - names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA"}) + names = {"PHASE0", "ALTAIR", "BELLATRIX", "CAPELLA", "DENEB", "ELECTRA", "FULU", "GLOAS"}) void emptyBlockRequestReturnsBadRequestStatus(final SpecMilestone specMilestone) throws JsonProcessingException { final Eth2BlockSigningRequestUtil util = new Eth2BlockSigningRequestUtil(specMilestone); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SigningAcceptanceTestBase.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SigningAcceptanceTestBase.java index cac546003..8e3aa8009 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SigningAcceptanceTestBase.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SigningAcceptanceTestBase.java @@ -105,6 +105,15 @@ private void setForkEpochs( builder.withElectraForkEpoch(0L); builder.withFuluForkEpoch(0L); } + case GLOAS -> { + builder.withAltairForkEpoch(0L); + builder.withBellatrixForkEpoch(0L); + builder.withCapellaForkEpoch(0L); + builder.withDenebForkEpoch(0L); + builder.withElectraForkEpoch(0L); + builder.withFuluForkEpoch(0L); + builder.withGloasForkEpoch(0L); + } default -> throw new IllegalStateException( "Setting manual fork epoch is not yet implemented for " + specMilestone); diff --git a/build.gradle b/build.gradle index 418ad1573..661b9fd9d 100644 --- a/build.gradle +++ b/build.gradle @@ -303,9 +303,18 @@ subprojects { from sourceSets.testSupport.output } + // TODO: Remove once Teku publishes its next release and the version is pinned in gradle/versions.gradle. + // Treat 'develop' versions of Teku as changing modules and bypass the 24h cache so Gradle always resolves the latest develop build. dependencies { testImplementation sourceSets.testSupport.output integrationTestImplementation sourceSets.testSupport.output + components { + all { ComponentMetadataDetails details -> + if (details.id.group.startsWith('tech.pegasys.teku') && details.id.version == 'develop') { + details.changing = true + } + } + } } configurations.configureEach { @@ -314,8 +323,9 @@ subprojects { exclude group: 'io.tmio', module: 'tuweni-crypto' exclude group: 'io.tmio', module: 'tuweni-rlp' exclude group: 'io.tmio', module: 'tuweni-units' - } + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' + } tasks.register('integrationTest', Test) { dependsOn "compileTestJava" diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2SubCommand.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2SubCommand.java index 8c6a91aaf..8bbec31e9 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2SubCommand.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/subcommands/Eth2SubCommand.java @@ -148,6 +148,15 @@ private static class NetworkCliCompletionCandidates extends ArrayList { converter = UInt64Converter.class) private UInt64 fuluForkEpoch; + @CommandLine.Option( + names = {"--Xnetwork-gloas-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Gloas fork activation epoch.", + arity = "1", + converter = UInt64Converter.class) + private UInt64 gloasForkEpoch; + @CommandLine.Option( names = {"--Xtrusted-setup"}, hidden = true, @@ -239,6 +248,9 @@ private Eth2NetworkConfiguration createEth2NetworkConfig() { if (fuluForkEpoch != null) { builder.fuluForkEpoch(fuluForkEpoch); } + if (gloasForkEpoch != null) { + builder.gloasForkEpoch(gloasForkEpoch); + } if (trustedSetup != null) { builder.trustedSetup(trustedSetup); } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/ArtifactType.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/ArtifactType.java index f361a8538..2b75fe198 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/ArtifactType.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/ArtifactType.java @@ -26,5 +26,10 @@ public enum ArtifactType { SYNC_COMMITTEE_MESSAGE, SYNC_COMMITTEE_SELECTION_PROOF, SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF, - VALIDATOR_REGISTRATION + VALIDATOR_REGISTRATION, + EXECUTION_PAYLOAD_BID, + EXECUTION_PAYLOAD_ENVELOPE, + PAYLOAD_ATTESTATION_MESSAGE, + PROPOSER_PREFERENCES, + BUILDER_REQUEST_AUTH } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java index 3b072cf5c..67dcdd59c 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java @@ -23,11 +23,16 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.json.BlockRequestDeserializer; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BLSPubKey; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BLSSignature; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment; import tech.pegasys.web3signer.core.service.http.serializers.BLSPubKeyDeserializer; import tech.pegasys.web3signer.core.service.http.serializers.BLSPubKeySerializer; import tech.pegasys.web3signer.core.service.http.serializers.BLSSignatureDeserializer; import tech.pegasys.web3signer.core.service.http.serializers.BLSSignatureSerializer; +import tech.pegasys.web3signer.core.service.http.serializers.KZGCommitmentDeserializer; +import tech.pegasys.web3signer.core.service.http.serializers.KZGCommitmentSerializer; import tech.pegasys.web3signer.core.service.http.serializers.SszBitvectorSerializer; +import tech.pegasys.web3signer.core.service.http.serializers.UInt256Deserializer; +import tech.pegasys.web3signer.core.service.http.serializers.UInt256Serializer; import tech.pegasys.web3signer.signing.config.metadata.parser.SigningMetadataModule; import tech.pegasys.web3signer.signing.config.metadata.parser.SigningMetadataModule.Bytes32Serializer; @@ -40,6 +45,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import org.apache.tuweni.units.bigints.UInt64; public class SigningObjectMapperFactory { @@ -83,6 +89,10 @@ private Module web3SignerMappers() { module.addDeserializer(BLSPubKey.class, new BLSPubKeyDeserializer()); module.addDeserializer(BLSSignature.class, new BLSSignatureDeserializer()); module.addSerializer(BLSSignature.class, new BLSSignatureSerializer()); + module.addSerializer(KZGCommitment.class, new KZGCommitmentSerializer()); + module.addDeserializer(KZGCommitment.class, new KZGCommitmentDeserializer()); + module.addSerializer(UInt256.class, new UInt256Serializer()); + module.addDeserializer(UInt256.class, new UInt256Deserializer()); module.addSerializer(SszBitvector.class, new SszBitvectorSerializer()); @@ -91,6 +101,13 @@ private Module web3SignerMappers() { module.addDeserializer(Bytes20.class, new SigningMetadataModule.Bytes20Deserializer()); module.addSerializer(Bytes20.class, new SigningMetadataModule.Bytes20Serializer()); + module.addDeserializer( + tech.pegasys.teku.bls.BLSPublicKey.class, + new SigningMetadataModule.BLSPublicKeyDeserializer()); + module.addSerializer( + tech.pegasys.teku.bls.BLSPublicKey.class, + new SigningMetadataModule.BLSPublicKeySerializer()); + return module; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SignForIdentifierHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SignForIdentifierHandler.java index 13929d582..9c6f5c5bc 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SignForIdentifierHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SignForIdentifierHandler.java @@ -20,6 +20,7 @@ import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncAggregatorSelectionDataSchema; import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; @@ -30,6 +31,11 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttestationData; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.ContributionAndProof; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.SyncCommitteeContribution; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences; import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics; import tech.pegasys.web3signer.core.util.DepositSigningRootUtil; import tech.pegasys.web3signer.slashingprotection.SlashingProtection; @@ -319,11 +325,62 @@ private Bytes computeSigningRoot(final Eth2SigningRequestBody body) { return signingRootUtil.signingRootForValidatorRegistration( validatorRegistration.asInternalValidatorRegistration()); } + case EXECUTION_PAYLOAD_BID -> { + final VersionedRequest request = body.executionPayloadBid(); + final SpecVersion specVersion = specVersionFor(request, "execution_payload_bid"); + return signingRootUtil.signingRootForSignExecutionPayloadBid( + request.data().asInternalExecutionPayloadBid(specVersion), + body.forkInfo().asInternalForkInfo()); + } + case EXECUTION_PAYLOAD_ENVELOPE -> { + final VersionedRequest request = body.executionPayloadEnvelope(); + final SpecVersion specVersion = specVersionFor(request, "execution_payload_envelope"); + return signingRootUtil.signingRootForSignExecutionPayloadEnvelope( + request.data().asInternalExecutionPayloadEnvelope(specVersion), + body.forkInfo().asInternalForkInfo()); + } + case PAYLOAD_ATTESTATION_MESSAGE -> { + final VersionedRequest request = body.payloadAttestationMessage(); + final SpecVersion specVersion = specVersionFor(request, "payload_attestation_message"); + return signingRootUtil.signingRootForSignPayloadAttestationData( + request.data().asInternalPayloadAttestationData(specVersion), + body.forkInfo().asInternalForkInfo()); + } + case PROPOSER_PREFERENCES -> { + final VersionedRequest request = body.proposerPreferences(); + final SpecVersion specVersion = specVersionFor(request, "proposer_preferences"); + return signingRootUtil.signingRootForSignProposerPreferences( + request.data().asInternalProposerPreferences(specVersion), + body.forkInfo().asInternalForkInfo()); + } + case BUILDER_REQUEST_AUTH -> { + final VersionedRequest request = body.builderRequestAuth(); + specVersionFor(request, "builder_request_auth"); + return signingRootUtil.signingRootForSignBuilderRequestAuth( + request.data().asInternalBuilderRequestAuth()); + } default -> throw new IllegalStateException("Signing root unimplemented for type " + body.type()); } } + /** + * Validates a fork-versioned payload and resolves the {@link SpecVersion} for its declared + * milestone. Rejects requests whose milestone is not scheduled on the configured network. + */ + private SpecVersion specVersionFor(final VersionedRequest request, final String field) { + checkArgument(request != null, "%s is required", field); + checkArgument(request.version() != null, "%s.version is required", field); + checkArgument(request.data() != null, "%s.data is required", field); + final SpecVersion specVersion = eth2Spec.forMilestone(request.version()); + checkArgument( + specVersion != null, + "%s.version %s is not scheduled on the configured network", + field, + request.version()); + return specVersion; + } + private tech.pegasys.teku.spec.datastructures.operations.versions.altair .SyncAggregatorSelectionData asInternalSyncAggregatorSelectionData( diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SigningRequestBody.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SigningRequestBody.java index f106b1a4a..c55dc665d 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SigningRequestBody.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/Eth2SigningRequestBody.java @@ -17,6 +17,11 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BeaconBlock; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.VoluntaryExit; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.altair.ContributionAndProof; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.BuilderRequestAuth; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadBid; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ExecutionPayloadEnvelope; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.PayloadAttestationData; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.gloas.ProposerPreferences; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; @@ -38,4 +43,13 @@ public record Eth2SigningRequestBody( @JsonProperty("sync_aggregator_selection_data") SyncAggregatorSelectionData syncAggregatorSelectionData, @JsonProperty("contribution_and_proof") ContributionAndProof contributionAndProof, - @JsonProperty("validator_registration") ValidatorRegistration validatorRegistration) {} + @JsonProperty("validator_registration") ValidatorRegistration validatorRegistration, + @JsonProperty("execution_payload_bid") + VersionedRequest executionPayloadBid, + @JsonProperty("execution_payload_envelope") + VersionedRequest executionPayloadEnvelope, + @JsonProperty("payload_attestation_message") + VersionedRequest payloadAttestationMessage, + @JsonProperty("proposer_preferences") VersionedRequest proposerPreferences, + @JsonProperty("builder_request_auth") + VersionedRequest builderRequestAuth) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/VersionedRequest.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/VersionedRequest.java new file mode 100644 index 000000000..a905633cc --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/VersionedRequest.java @@ -0,0 +1,26 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2; + +import tech.pegasys.teku.spec.SpecMilestone; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Fork-versioned signing payload: {@code {"version": "", "data": {...}}}. Used by + * the Glamsterdam (ePBS) signing types so the signer can pick the SSZ schema for the named fork + * rather than inferring it from the slot. + */ +public record VersionedRequest( + @JsonProperty(value = "version", required = true) SpecMilestone version, + @JsonProperty(value = "data", required = true) T data) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BeaconBlockBodyElectra.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BeaconBlockBodyElectra.java index 6b0ee5d97..80305bc26 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BeaconBlockBodyElectra.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BeaconBlockBodyElectra.java @@ -140,7 +140,7 @@ public BeaconBlockBody asInternalBeaconBlockBody(final SpecVersion spec) { .map(SszKZGCommitment::new) .collect(blobKZGCommitmentsSchema.collector())); builder.executionRequests( - this.executionRequests.asInternalConsolidationRequest( + this.executionRequests.asInternalExecutionRequests( SchemaDefinitionsElectra.required(spec.getSchemaDefinitions()) .getExecutionRequestsSchema())); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BlindedBeaconBlockBodyElectra.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BlindedBeaconBlockBodyElectra.java index f14ca5923..9d18b6e37 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BlindedBeaconBlockBodyElectra.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BlindedBeaconBlockBodyElectra.java @@ -20,7 +20,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodySchemaElectra; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.Attestation; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.AttesterSlashing; @@ -135,7 +135,7 @@ public BeaconBlockBody asInternalBeaconBlockBody(final SpecVersion spec) { final SszListSchema blobKZGCommitmentsSchema = getBeaconBlockBodySchema(spec).getBlobKzgCommitmentsSchema(); - final ExecutionRequestsSchema executionRequestsSchema = + final ExecutionRequestsSchema executionRequestsSchema = getBeaconBlockBodySchema(spec).getExecutionRequestsSchema(); return super.asInternalBeaconBlockBody( @@ -154,7 +154,7 @@ public BeaconBlockBody asInternalBeaconBlockBody(final SpecVersion spec) { .map(SszKZGCommitment::new) .collect(blobKZGCommitmentsSchema.collector())); builder.executionRequests( - this.executionRequests.asInternalConsolidationRequest(executionRequestsSchema)); + this.executionRequests.asInternalExecutionRequests(executionRequestsSchema)); return SafeFuture.COMPLETE; }); } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderDepositRequest.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderDepositRequest.java new file mode 100644 index 000000000..48065fa18 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderDepositRequest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.electra; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderDepositRequestSchema; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BLSPubKey; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.BLSSignature; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +/** + * EIP-8282 builder deposit request. Introduced in Gloas (ePBS) as one of the execution requests + * that registers a builder via the deposit contract. + */ +public class BuilderDepositRequest { + + @JsonProperty("pubkey") + private final BLSPubKey pubkey; + + @JsonProperty("withdrawal_credentials") + private final Bytes32 withdrawalCredentials; + + @JsonProperty("amount") + private final UInt64 amount; + + @JsonProperty("signature") + private final BLSSignature signature; + + public BuilderDepositRequest( + @JsonProperty("pubkey") final BLSPubKey pubkey, + @JsonProperty("withdrawal_credentials") final Bytes32 withdrawalCredentials, + @JsonProperty("amount") final UInt64 amount, + @JsonProperty("signature") final BLSSignature signature) { + this.pubkey = pubkey; + this.withdrawalCredentials = withdrawalCredentials; + this.amount = amount; + this.signature = signature; + } + + public BuilderDepositRequest( + final tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderDepositRequest + builderDepositRequest) { + this.pubkey = new BLSPubKey(builderDepositRequest.getPubkey()); + this.withdrawalCredentials = builderDepositRequest.getWithdrawalCredentials(); + this.amount = builderDepositRequest.getAmount(); + this.signature = new BLSSignature(builderDepositRequest.getSignature()); + } + + public final tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderDepositRequest + asInternalBuilderDepositRequest(final BuilderDepositRequestSchema schema) { + return schema.create( + pubkey.asBLSPublicKey(), withdrawalCredentials, amount, signature.asInternalBLSSignature()); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderExitRequest.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderExitRequest.java new file mode 100644 index 000000000..53136acd9 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/BuilderExitRequest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.electra; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.ethereum.execution.types.Eth1Address; +import tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderExitRequestSchema; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * EIP-8282 builder exit request. Introduced in Gloas (ePBS) as one of the execution requests that + * voluntarily exits a registered builder. + */ +public class BuilderExitRequest { + + @JsonProperty("source_address") + private final Eth1Address sourceAddress; + + @JsonProperty("pubkey") + private final BLSPublicKey pubkey; + + public BuilderExitRequest( + @JsonProperty("source_address") final Eth1Address sourceAddress, + @JsonProperty("pubkey") final BLSPublicKey pubkey) { + this.sourceAddress = sourceAddress; + this.pubkey = pubkey; + } + + public BuilderExitRequest( + final tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderExitRequest + builderExitRequest) { + this.sourceAddress = + Eth1Address.fromBytes(builderExitRequest.getSourceAddress().getWrappedBytes()); + this.pubkey = builderExitRequest.getPubkey(); + } + + public final tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderExitRequest + asInternalBuilderExitRequest(final BuilderExitRequestSchema schema) { + return schema.create(sourceAddress, pubkey); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/ExecutionRequests.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/ExecutionRequests.java index fbb4a5260..7319f41d4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/ExecutionRequests.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/electra/ExecutionRequests.java @@ -12,14 +12,19 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.electra; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionRequestsBuilder; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ConsolidationRequestSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositRequestSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequestsSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.WithdrawalRequestSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderDepositRequestSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderExitRequestSchema; +import tech.pegasys.teku.spec.datastructures.execution.versions.gloas.ExecutionRequestsSchemaGloas; import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; public class ExecutionRequests { @@ -32,27 +37,53 @@ public class ExecutionRequests { @JsonProperty("consolidations") private final List consolidations; + // Gloas (ePBS) additions, EIP-8282. Absent on pre-Gloas forks. + @JsonProperty("builder_deposits") + private final List builderDeposits; + + @JsonProperty("builder_exits") + private final List builderExits; + public ExecutionRequests( @JsonProperty("deposits") final List deposits, @JsonProperty("withdrawals") final List withdrawals, - @JsonProperty("consolidations") final List consolidations) { + @JsonProperty("consolidations") final List consolidations, + @JsonProperty("builder_deposits") final List builderDeposits, + @JsonProperty("builder_exits") final List builderExits) { this.deposits = deposits; this.withdrawals = withdrawals; this.consolidations = consolidations; + this.builderDeposits = MoreObjects.firstNonNull(builderDeposits, List.of()); + this.builderExits = MoreObjects.firstNonNull(builderExits, List.of()); } public ExecutionRequests( - final tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests - executionRequests) { + final tech.pegasys.teku.spec.datastructures.execution.ExecutionRequests executionRequests) { this.deposits = executionRequests.getDeposits().stream().map(DepositRequest::new).toList(); this.withdrawals = executionRequests.getWithdrawals().stream().map(WithdrawalRequest::new).toList(); this.consolidations = executionRequests.getConsolidations().stream().map(ConsolidationRequest::new).toList(); + if (executionRequests + instanceof + tech.pegasys.teku.spec.datastructures.execution.versions.gloas.ExecutionRequestsGloas + gloasRequests) { + this.builderDeposits = + gloasRequests.getBuilderDeposits().stream().map(BuilderDepositRequest::new).toList(); + this.builderExits = + gloasRequests.getBuilderExits().stream().map(BuilderExitRequest::new).toList(); + } else { + this.builderDeposits = List.of(); + this.builderExits = List.of(); + } } - public final tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionRequests - asInternalConsolidationRequest(final ExecutionRequestsSchema schema) { + /** + * Builds the internal execution requests for the given schema. Electra schemas ignore builder + * deposits/exits (no-op); Gloas schemas (EIP-8282) require them. + */ + public final tech.pegasys.teku.spec.datastructures.execution.ExecutionRequests + asInternalExecutionRequests(final ExecutionRequestsSchema schema) { final DepositRequestSchema depositSchema = (DepositRequestSchema) schema.getDepositRequestsSchema().getElementSchema(); @@ -81,6 +112,38 @@ public ExecutionRequests( consolidationRequest -> consolidationRequest.asInternalConsolidationRequest(consolidationSchema)) .toList(); - return schema.create(depositsInternal, withdrawalsInternal, consolidationsInternal); + + final ExecutionRequestsBuilder builder = schema.createBuilder(); + builder.deposits(depositsInternal); + builder.withdrawals(withdrawalsInternal); + builder.consolidations(consolidationsInternal); + + if (schema instanceof ExecutionRequestsSchemaGloas gloasSchema) { + final BuilderDepositRequestSchema builderDepositSchema = + (BuilderDepositRequestSchema) + gloasSchema.getBuilderDepositRequestsSchema().getElementSchema(); + final BuilderExitRequestSchema builderExitSchema = + (BuilderExitRequestSchema) gloasSchema.getBuilderExitRequestsSchema().getElementSchema(); + final List< + tech.pegasys.teku.spec.datastructures.execution.versions.gloas.BuilderDepositRequest> + builderDepositsInternal = + builderDeposits.stream() + .map( + builderDepositRequest -> + builderDepositRequest.asInternalBuilderDepositRequest( + builderDepositSchema)) + .toList(); + final List + builderExitsInternal = + builderExits.stream() + .map( + builderExitRequest -> + builderExitRequest.asInternalBuilderExitRequest(builderExitSchema)) + .toList(); + builder.builderDeposits(() -> builderDepositsInternal); + builder.builderExits(() -> builderExitsInternal); + } + + return builder.build(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/BuilderRequestAuth.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/BuilderRequestAuth.java new file mode 100644 index 000000000..1e96e4635 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/BuilderRequestAuth.java @@ -0,0 +1,54 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.schemas.ApiSchemas; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes; + +/** + * Proposer-signed, out-of-protocol builder-API auth message (builder-specs, Gloas). Signed with + * DOMAIN_BUILDER_REQUEST_AUTH (0x0B000001), computed from the genesis fork version and a zero + * genesis_validators_root (no fork_info required), not the proposer's current fork. + */ +public class BuilderRequestAuth { + + private final Bytes data; + private final UInt64 slot; + + @JsonCreator + public BuilderRequestAuth( + @JsonProperty(value = "data", required = true) final Bytes data, + @JsonProperty(value = "slot", required = true) final UInt64 slot) { + this.data = data; + this.slot = slot; + } + + @JsonProperty("data") + public Bytes getData() { + return data; + } + + @JsonProperty("slot") + public UInt64 getSlot() { + return slot; + } + + public tech.pegasys.teku.spec.datastructures.builder.versions.gloas.BuilderRequestAuth + asInternalBuilderRequestAuth() { + return ApiSchemas.BUILDER_REQUEST_AUTH_SCHEMA.create(data, slot); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadBid.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadBid.java new file mode 100644 index 000000000..3bad1177b --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadBid.java @@ -0,0 +1,158 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBidSchema; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsGloas; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public class ExecutionPayloadBid { + + private final Bytes32 parentBlockHash; + private final Bytes32 parentBlockRoot; + private final Bytes32 blockHash; + private final Bytes32 prevRandao; + private final Bytes20 feeRecipient; + private final UInt64 gasLimit; + private final UInt64 builderIndex; + private final UInt64 slot; + private final UInt64 value; + private final UInt64 executionPayment; + private final List blobKzgCommitments; + private final Bytes32 executionRequestsRoot; + + @JsonCreator + public ExecutionPayloadBid( + @JsonProperty(value = "parent_block_hash", required = true) final Bytes32 parentBlockHash, + @JsonProperty(value = "parent_block_root", required = true) final Bytes32 parentBlockRoot, + @JsonProperty(value = "block_hash", required = true) final Bytes32 blockHash, + @JsonProperty(value = "prev_randao", required = true) final Bytes32 prevRandao, + @JsonProperty(value = "fee_recipient", required = true) final Bytes20 feeRecipient, + @JsonProperty(value = "gas_limit", required = true) final UInt64 gasLimit, + @JsonProperty(value = "builder_index", required = true) final UInt64 builderIndex, + @JsonProperty(value = "slot", required = true) final UInt64 slot, + @JsonProperty(value = "value", required = true) final UInt64 value, + @JsonProperty(value = "execution_payment", required = true) final UInt64 executionPayment, + @JsonProperty(value = "blob_kzg_commitments", required = true) + final List blobKzgCommitments, + @JsonProperty(value = "execution_requests_root", required = true) + final Bytes32 executionRequestsRoot) { + this.parentBlockHash = parentBlockHash; + this.parentBlockRoot = parentBlockRoot; + this.blockHash = blockHash; + this.prevRandao = prevRandao; + this.feeRecipient = feeRecipient; + this.gasLimit = gasLimit; + this.builderIndex = builderIndex; + this.slot = slot; + this.value = value; + this.executionPayment = executionPayment; + this.blobKzgCommitments = blobKzgCommitments; + this.executionRequestsRoot = executionRequestsRoot; + } + + @JsonProperty("parent_block_hash") + public Bytes32 getParentBlockHash() { + return parentBlockHash; + } + + @JsonProperty("parent_block_root") + public Bytes32 getParentBlockRoot() { + return parentBlockRoot; + } + + @JsonProperty("block_hash") + public Bytes32 getBlockHash() { + return blockHash; + } + + @JsonProperty("prev_randao") + public Bytes32 getPrevRandao() { + return prevRandao; + } + + @JsonProperty("fee_recipient") + public Bytes20 getFeeRecipient() { + return feeRecipient; + } + + @JsonProperty("gas_limit") + public UInt64 getGasLimit() { + return gasLimit; + } + + @JsonProperty("builder_index") + public UInt64 getBuilderIndex() { + return builderIndex; + } + + @JsonProperty("slot") + public UInt64 getSlot() { + return slot; + } + + @JsonProperty("value") + public UInt64 getValue() { + return value; + } + + @JsonProperty("execution_payment") + public UInt64 getExecutionPayment() { + return executionPayment; + } + + @JsonProperty("blob_kzg_commitments") + public List getBlobKzgCommitments() { + return blobKzgCommitments; + } + + @JsonProperty("execution_requests_root") + public Bytes32 getExecutionRequestsRoot() { + return executionRequestsRoot; + } + + public tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadBid + asInternalExecutionPayloadBid(final SpecVersion specVersion) { + final ExecutionPayloadBidSchema schema = + SchemaDefinitionsGloas.required(specVersion.getSchemaDefinitions()) + .getExecutionPayloadBidSchema(); + final SszList sszBlobKzgCommitments = + blobKzgCommitments.stream() + .map(c -> new SszKZGCommitment(c.asInternalKZGCommitment())) + .collect(schema.getBlobKzgCommitmentsSchema().collector()); + return schema.create( + parentBlockHash, + parentBlockRoot, + blockHash, + prevRandao, + feeRecipient, + gasLimit, + builderIndex, + slot, + value, + executionPayment, + sszBlobKzgCommitments, + executionRequestsRoot); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadEnvelope.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadEnvelope.java new file mode 100644 index 000000000..f9edcd316 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadEnvelope.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsGloas; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.electra.ExecutionRequests; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public class ExecutionPayloadEnvelope { + + private final ExecutionPayloadGloas payload; + private final ExecutionRequests executionRequests; + private final UInt64 builderIndex; + private final Bytes32 beaconBlockRoot; + private final Bytes32 parentBeaconBlockRoot; + + @JsonCreator + public ExecutionPayloadEnvelope( + @JsonProperty(value = "payload", required = true) final ExecutionPayloadGloas payload, + @JsonProperty(value = "execution_requests", required = true) + final ExecutionRequests executionRequests, + @JsonProperty(value = "builder_index", required = true) final UInt64 builderIndex, + @JsonProperty(value = "beacon_block_root", required = true) final Bytes32 beaconBlockRoot, + @JsonProperty(value = "parent_beacon_block_root", required = true) + final Bytes32 parentBeaconBlockRoot) { + this.payload = payload; + this.executionRequests = executionRequests; + this.builderIndex = builderIndex; + this.beaconBlockRoot = beaconBlockRoot; + this.parentBeaconBlockRoot = parentBeaconBlockRoot; + } + + @JsonProperty("payload") + public ExecutionPayloadGloas getPayload() { + return payload; + } + + @JsonProperty("execution_requests") + public ExecutionRequests getExecutionRequests() { + return executionRequests; + } + + @JsonProperty("builder_index") + public UInt64 getBuilderIndex() { + return builderIndex; + } + + @JsonProperty("beacon_block_root") + public Bytes32 getBeaconBlockRoot() { + return beaconBlockRoot; + } + + @JsonProperty("parent_beacon_block_root") + public Bytes32 getParentBeaconBlockRoot() { + return parentBeaconBlockRoot; + } + + /** The slot lives on the inner payload in Glamsterdam (ePBS); delegated for handler use. */ + public UInt64 getSlot() { + return payload.slotNumber; + } + + public tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ExecutionPayloadEnvelope + asInternalExecutionPayloadEnvelope(final SpecVersion specVersion) { + final SchemaDefinitionsGloas gloasSchemas = + SchemaDefinitionsGloas.required(specVersion.getSchemaDefinitions()); + return gloasSchemas + .getExecutionPayloadEnvelopeSchema() + .create( + payload.asInternalExecutionPayload(specVersion), + executionRequests.asInternalExecutionRequests( + SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) + .getExecutionRequestsSchema()), + builderIndex, + beaconBlockRoot, + parentBeaconBlockRoot); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadGloas.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadGloas.java new file mode 100644 index 000000000..64c31046a --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ExecutionPayloadGloas.java @@ -0,0 +1,140 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.infrastructure.bytes.Bytes20; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; +import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.capella.Withdrawal; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.deneb.ExecutionPayloadDeneb; + +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.MoreObjects; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; + +public class ExecutionPayloadGloas extends ExecutionPayloadDeneb { + + @JsonProperty("block_access_list") + public final Bytes blockAccessList; + + @JsonProperty("slot_number") + public final UInt64 slotNumber; + + @JsonCreator + public ExecutionPayloadGloas( + @JsonProperty("parent_hash") final Bytes32 parentHash, + @JsonProperty("fee_recipient") final Bytes20 feeRecipient, + @JsonProperty("state_root") final Bytes32 stateRoot, + @JsonProperty("receipts_root") final Bytes32 receiptsRoot, + @JsonProperty("logs_bloom") final Bytes logsBloom, + @JsonProperty("prev_randao") final Bytes32 prevRandao, + @JsonProperty("block_number") final UInt64 blockNumber, + @JsonProperty("gas_limit") final UInt64 gasLimit, + @JsonProperty("gas_used") final UInt64 gasUsed, + @JsonProperty("timestamp") final UInt64 timestamp, + @JsonProperty("extra_data") final Bytes extraData, + @JsonProperty("base_fee_per_gas") final UInt256 baseFeePerGas, + @JsonProperty("block_hash") final Bytes32 blockHash, + @JsonProperty("transactions") final List transactions, + @JsonProperty("withdrawals") final List withdrawals, + @JsonProperty("blob_gas_used") final UInt64 blobGasUsed, + @JsonProperty("excess_blob_gas") final UInt64 excessBlobGas, + @JsonProperty("block_access_list") final Bytes blockAccessList, + @JsonProperty("slot_number") final UInt64 slotNumber) { + super( + parentHash, + feeRecipient, + stateRoot, + receiptsRoot, + logsBloom, + prevRandao, + blockNumber, + gasLimit, + gasUsed, + timestamp, + extraData, + baseFeePerGas, + blockHash, + transactions, + withdrawals, + blobGasUsed, + excessBlobGas); + this.blockAccessList = blockAccessList; + this.slotNumber = slotNumber; + } + + public ExecutionPayloadGloas( + final tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload executionPayload) { + super(executionPayload); + final tech.pegasys.teku.spec.datastructures.execution.versions.gloas.ExecutionPayloadGloas + gloasPayload = + tech.pegasys.teku.spec.datastructures.execution.versions.gloas.ExecutionPayloadGloas + .required(executionPayload); + this.blockAccessList = gloasPayload.getBlockAccessList().getBytes(); + this.slotNumber = gloasPayload.getSlotNumber(); + } + + @Override + protected ExecutionPayloadBuilder applyToBuilder( + final ExecutionPayloadSchema executionPayloadSchema, + final ExecutionPayloadBuilder builder) { + return super.applyToBuilder(executionPayloadSchema, builder) + .blockAccessList(() -> blockAccessList) + .slotNumber(() -> slotNumber); + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof ExecutionPayloadGloas that)) return false; + if (!super.equals(o)) return false; + return Objects.equals(blockAccessList, that.blockAccessList) + && Objects.equals(slotNumber, that.slotNumber); + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), blockAccessList, slotNumber); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("parentHash", parentHash) + .add("feeRecipient", feeRecipient) + .add("stateRoot", stateRoot) + .add("receiptsRoot", receiptsRoot) + .add("logsBloom", logsBloom) + .add("prevRandao", prevRandao) + .add("blockNumber", blockNumber) + .add("gasLimit", gasLimit) + .add("gasUsed", gasUsed) + .add("timestamp", timestamp) + .add("extraData", extraData) + .add("baseFeePerGas", baseFeePerGas) + .add("blockHash", blockHash) + .add("transactions", transactions) + .add("withdrawals", withdrawals) + .add("blobGasUsed", blobGasUsed) + .add("excessBlobGas", excessBlobGas) + .add("blockAccessList", blockAccessList) + .add("slotNumber", slotNumber) + .toString(); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/PayloadAttestationData.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/PayloadAttestationData.java new file mode 100644 index 000000000..686789a8c --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/PayloadAttestationData.java @@ -0,0 +1,69 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsGloas; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public class PayloadAttestationData { + + private final Bytes32 beaconBlockRoot; + private final UInt64 slot; + private final boolean payloadPresent; + private final boolean blobDataAvailable; + + @JsonCreator + public PayloadAttestationData( + @JsonProperty(value = "beacon_block_root", required = true) final Bytes32 beaconBlockRoot, + @JsonProperty(value = "slot", required = true) final UInt64 slot, + @JsonProperty(value = "payload_present", required = true) final boolean payloadPresent, + @JsonProperty(value = "blob_data_available", required = true) + final boolean blobDataAvailable) { + this.beaconBlockRoot = beaconBlockRoot; + this.slot = slot; + this.payloadPresent = payloadPresent; + this.blobDataAvailable = blobDataAvailable; + } + + @JsonProperty("beacon_block_root") + public Bytes32 getBeaconBlockRoot() { + return beaconBlockRoot; + } + + @JsonProperty("slot") + public UInt64 getSlot() { + return slot; + } + + @JsonProperty("payload_present") + public boolean isPayloadPresent() { + return payloadPresent; + } + + @JsonProperty("blob_data_available") + public boolean isBlobDataAvailable() { + return blobDataAvailable; + } + + public tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.PayloadAttestationData + asInternalPayloadAttestationData(final SpecVersion specVersion) { + return SchemaDefinitionsGloas.required(specVersion.getSchemaDefinitions()) + .getPayloadAttestationDataSchema() + .create(beaconBlockRoot, slot, payloadPresent, blobDataAvailable); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ProposerPreferences.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ProposerPreferences.java new file mode 100644 index 000000000..c9ed3f097 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/schema/gloas/ProposerPreferences.java @@ -0,0 +1,77 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.handlers.signing.eth2.schema.gloas; + +import tech.pegasys.teku.ethereum.execution.types.Eth1Address; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsGloas; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public class ProposerPreferences { + + private final Bytes32 dependentRoot; + private final UInt64 proposalSlot; + private final UInt64 validatorIndex; + private final Eth1Address feeRecipient; + private final UInt64 targetGasLimit; + + @JsonCreator + public ProposerPreferences( + @JsonProperty(value = "dependent_root", required = true) final Bytes32 dependentRoot, + @JsonProperty(value = "proposal_slot", required = true) final UInt64 proposalSlot, + @JsonProperty(value = "validator_index", required = true) final UInt64 validatorIndex, + @JsonProperty(value = "fee_recipient", required = true) final Eth1Address feeRecipient, + @JsonProperty(value = "target_gas_limit", required = true) final UInt64 targetGasLimit) { + this.dependentRoot = dependentRoot; + this.proposalSlot = proposalSlot; + this.validatorIndex = validatorIndex; + this.feeRecipient = feeRecipient; + this.targetGasLimit = targetGasLimit; + } + + @JsonProperty("dependent_root") + public Bytes32 getDependentRoot() { + return dependentRoot; + } + + @JsonProperty("proposal_slot") + public UInt64 getProposalSlot() { + return proposalSlot; + } + + @JsonProperty("validator_index") + public UInt64 getValidatorIndex() { + return validatorIndex; + } + + @JsonProperty("fee_recipient") + public Eth1Address getFeeRecipient() { + return feeRecipient; + } + + @JsonProperty("target_gas_limit") + public UInt64 getTargetGasLimit() { + return targetGasLimit; + } + + public tech.pegasys.teku.spec.datastructures.epbs.versions.gloas.ProposerPreferences + asInternalProposerPreferences(final SpecVersion specVersion) { + return SchemaDefinitionsGloas.required(specVersion.getSchemaDefinitions()) + .getProposerPreferencesSchema() + .create(dependentRoot, proposalSlot, validatorIndex, feeRecipient, targetGasLimit); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentDeserializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentDeserializer.java new file mode 100644 index 000000000..4e9ad0b6c --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentDeserializer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.serializers; + +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import org.apache.tuweni.bytes.Bytes; + +public class KZGCommitmentDeserializer extends JsonDeserializer { + public KZGCommitmentDeserializer() {} + + @Override + public KZGCommitment deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return new KZGCommitment(Bytes.fromHexString(p.getValueAsString())); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentSerializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentSerializer.java new file mode 100644 index 000000000..3ae5efc2c --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/KZGCommitmentSerializer.java @@ -0,0 +1,32 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.serializers; + +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.schema.KZGCommitment; + +import java.io.IOException; +import java.util.Locale; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class KZGCommitmentSerializer extends JsonSerializer { + public KZGCommitmentSerializer() {} + + @Override + public void serialize(KZGCommitment value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeString(value.toHexString().toLowerCase(Locale.ROOT)); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Deserializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Deserializer.java new file mode 100644 index 000000000..312438fdc --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Deserializer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.serializers; + +import java.io.IOException; +import java.math.BigInteger; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import org.apache.tuweni.units.bigints.UInt256; + +/** UInt256 is represented as a decimal string on the wire (e.g. base_fee_per_gas). */ +public class UInt256Deserializer extends JsonDeserializer { + public UInt256Deserializer() {} + + @Override + public UInt256 deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return UInt256.valueOf(new BigInteger(p.getValueAsString())); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Serializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Serializer.java new file mode 100644 index 000000000..38e26d771 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/serializers/UInt256Serializer.java @@ -0,0 +1,31 @@ +/* + * Copyright 2026 ConsenSys AG. + * + * 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.web3signer.core.service.http.serializers; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import org.apache.tuweni.units.bigints.UInt256; + +/** UInt256 is represented as a decimal string on the wire (e.g. base_fee_per_gas). */ +public class UInt256Serializer extends JsonSerializer { + public UInt256Serializer() {} + + @Override + public void serialize(UInt256 value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeString(value.toBigInteger().toString()); + } +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 95bf94e71..0733879a6 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -87,7 +87,7 @@ dependencyManagement { dependency 'org.xipki.iaik:sunpkcs11-wrapper:1.4.10' - dependencySet(group: 'tech.pegasys.teku.internal', version: '26.4.0') { + dependencySet(group: 'tech.pegasys.teku.internal', version: 'develop') { entry ('bls') { exclude group: 'org.bouncycastle', name: 'bcprov-jdk15on' } diff --git a/openapi-specs/eth2/signing/paths/sign.yaml b/openapi-specs/eth2/signing/paths/sign.yaml index 58c9f0d8a..7f943564e 100644 --- a/openapi-specs/eth2/signing/paths/sign.yaml +++ b/openapi-specs/eth2/signing/paths/sign.yaml @@ -31,6 +31,11 @@ post: - $ref: '../schemas.yaml#/components/schemas/SyncCommitteeSelectionProofSigning' - $ref: '../schemas.yaml#/components/schemas/SyncCommitteeContributionAndProofSigning' - $ref: '../schemas.yaml#/components/schemas/ValidatorRegistrationSigning' + - $ref: '../schemas.yaml#/components/schemas/ExecutionPayloadBidSigning' + - $ref: '../schemas.yaml#/components/schemas/ExecutionPayloadEnvelopeSigning' + - $ref: '../schemas.yaml#/components/schemas/PayloadAttestationMessageSigning' + - $ref: '../schemas.yaml#/components/schemas/ProposerPreferencesSigning' + - $ref: '../schemas.yaml#/components/schemas/BuilderRequestAuthSigning' discriminator: propertyName: type mapping: @@ -47,7 +52,30 @@ post: SYNC_COMMITTEE_SELECTION_PROOF: '../schemas.yaml#/components/schemas/SyncCommitteeSelectionProofSigning' SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF: '../schemas.yaml#/components/schemas/SyncCommitteeContributionAndProofSigning' VALIDATOR_REGISTRATION: '../schemas.yaml#/components/schemas/ValidatorRegistrationSigning' + EXECUTION_PAYLOAD_BID: '../schemas.yaml#/components/schemas/ExecutionPayloadBidSigning' + EXECUTION_PAYLOAD_ENVELOPE: '../schemas.yaml#/components/schemas/ExecutionPayloadEnvelopeSigning' + PAYLOAD_ATTESTATION_MESSAGE: '../schemas.yaml#/components/schemas/PayloadAttestationMessageSigning' + PROPOSER_PREFERENCES: '../schemas.yaml#/components/schemas/ProposerPreferencesSigning' + BUILDER_REQUEST_AUTH: '../schemas.yaml#/components/schemas/BuilderRequestAuthSigning' examples: + BLOCK_V2 (GLOAS): + value: + type: "BLOCK_V2" + signingRoot: "0xaa2e0c465c1a45d7b6637fcce4ad6ceb71fc12064b548078d619a411f0de8adc" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + beacon_block: + version: "GLOAS" + block_header: + slot: "0" + proposer_index: "4666673844721362956" + parent_root: "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef" + state_root: "0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e" + body_root: "0xa759d8029a69d4fdd8b3996086e9722983977e4efc1f12f4098ea3d93e868a6b" BLOCK_V2 (FULU): value: type: "BLOCK_V2" @@ -513,6 +541,35 @@ post: genesis_validators_root: '0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673' aggregation_slot: slot: "119" + AGGREGATE_AND_PROOF_V2 (GLOAS): + value: + type: "AGGREGATE_AND_PROOF_V2" + signingRoot: "0xcbc14a290b4a07a5c9302b2d7467fe7694a5dc2c93167f6a1e5662dd06b8501a" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + aggregate_and_proof: + version: "GLOAS" + data: + aggregator_index: "1" + aggregate: + aggregation_bits: "0x0000000000000000000000000000000000000000000101" + data: + slot: "0" + index: "0" + beacon_block_root: "0x100814c335d0ced5014cfa9d2e375e6d9b4e197381f8ce8af0473200fdc917fd" + source: + epoch: "0" + root: "0x0000000000000000000000000000000000000000000000000000000000000000" + target: + epoch: "0" + root: "0x100814c335d0ced5014cfa9d2e375e6d9b4e197381f8ce8af0473200fdc917fd" + signature: "0xa627242e4a5853708f4ebf923960fb8192f93f2233cd347e05239d86dd9fb66b721ceec1baeae6647f498c9126074f1101a87854d674b6eebc220fd8c3d8405bdfd8e286b707975d9e00a56ec6cbbf762f23607d490f0bbb16c3e0e483d51875" + committee_bits: "0x0000000000000001" + selection_proof: "0xa63f73a03f1f42b1fd0a988b614d511eb346d0a91c809694ef76df5ae021f0f144d64e612d735bc8820950cf6f7f84cd0ae194bfe3d4242fe79688f83462e3f69d9d33de71aab0721b7dab9d6960875e5fdfd26b171a75fb51af822043820c47" AGGREGATE_AND_PROOF_V2 (FULU): value: type: "AGGREGATE_AND_PROOF_V2" @@ -833,6 +890,117 @@ post: amount: "32" genesis_fork_version: "0x00000001" + EXECUTION_PAYLOAD_BID (GLOAS): + value: + type: "EXECUTION_PAYLOAD_BID" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + execution_payload_bid: + version: "GLOAS" + data: + parent_block_hash: "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef" + parent_block_root: "0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e" + block_hash: "0xa759d8029a69d4fdd8b3996086e9722983977e4efc1f12f4098ea3d93e868a6b" + prev_randao: "0x100814c335d0ced5014cfa9d2e375e6d9b4e197381f8ce8af0473200fdc917fd" + fee_recipient: "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a9" + gas_limit: "30000000" + builder_index: "1" + slot: "1" + value: "1000000000" + execution_payment: "0" + blob_kzg_commitments: [] + execution_requests_root: "0x0000000000000000000000000000000000000000000000000000000000000000" + + EXECUTION_PAYLOAD_ENVELOPE (GLOAS): + value: + type: "EXECUTION_PAYLOAD_ENVELOPE" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + execution_payload_envelope: + version: "GLOAS" + data: + payload: + parent_hash: "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef" + fee_recipient: "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a9" + state_root: "0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e" + receipts_root: "0x0000000000000000000000000000000000000000000000000000000000000000" + logs_bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + prev_randao: "0x100814c335d0ced5014cfa9d2e375e6d9b4e197381f8ce8af0473200fdc917fd" + block_number: "1" + gas_limit: "30000000" + gas_used: "0" + timestamp: "1" + extra_data: "0x" + base_fee_per_gas: "1" + block_hash: "0xa759d8029a69d4fdd8b3996086e9722983977e4efc1f12f4098ea3d93e868a6b" + transactions: [] + withdrawals: [] + blob_gas_used: "0" + excess_blob_gas: "0" + block_access_list: "0x" + slot_number: "1" + execution_requests: + deposits: [] + withdrawals: [] + consolidations: [] + builder_deposits: [] + builder_exits: [] + builder_index: "1" + beacon_block_root: "0x235bc3400c2839fd856a524871200bd5e362db615fc4565e1870ed9a2a936464" + parent_beacon_block_root: "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef" + + PAYLOAD_ATTESTATION_MESSAGE (GLOAS): + value: + type: "PAYLOAD_ATTESTATION_MESSAGE" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + payload_attestation_message: + version: "GLOAS" + data: + beacon_block_root: "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" + slot: "1" + payload_present: true + blob_data_available: true + + PROPOSER_PREFERENCES (GLOAS): + value: + type: "PROPOSER_PREFERENCES" + fork_info: + fork: + previous_version: "0x00000001" + current_version: "0x00000001" + epoch: "1" + genesis_validators_root: "0x04700007fabc8282644aed6d1c7c9e21d38a03a0c4ba193f3afe428824b3a673" + proposer_preferences: + version: "GLOAS" + data: + dependent_root: "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef" + proposal_slot: "32" + validator_index: "1" + fee_recipient: "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a9" + target_gas_limit: "60000000" + + BUILDER_REQUEST_AUTH (GLOAS): + value: + type: "BUILDER_REQUEST_AUTH" + builder_request_auth: + version: "GLOAS" + data: + data: "0x68747470733a2f2f6275696c6465722e6578616d706c652e6f7267" + slot: "32" + responses: '200': description: 'hex encoded string of signature' diff --git a/openapi-specs/eth2/signing/schemas.yaml b/openapi-specs/eth2/signing/schemas.yaml index 4c33907fc..0beddd341 100644 --- a/openapi-specs/eth2/signing/schemas.yaml +++ b/openapi-specs/eth2/signing/schemas.yaml @@ -89,6 +89,7 @@ components: - $ref: '#/components/schemas/AggregateAndProofRequestDeneb' - $ref: '#/components/schemas/AggregateAndProofRequestElectra' - $ref: '#/components/schemas/AggregateAndProofRequestFulu' + - $ref: '#/components/schemas/AggregateAndProofRequestGloas' discriminator: propertyName: version mapping: @@ -99,6 +100,7 @@ components: DENEB: '#/components/schemas/AggregateAndProofRequestDeneb' ELECTRA: '#/components/schemas/AggregateAndProofRequestElectra' FULU: '#/components/schemas/AggregateAndProofRequestFulu' + GLOAS: '#/components/schemas/AggregateAndProofRequestGloas' required: - aggregate_and_proof AggregateAndProofRequestPhase0: @@ -192,6 +194,19 @@ components: required: - version - data + AggregateAndProofRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify AggregateAndProof request type.' + data: + $ref: "#/components/schemas/AggregateAndProofElectra" + required: + - version + - data AttestationSigning: allOf: - $ref: '#/components/schemas/Signing' @@ -341,6 +356,198 @@ components: required: - type - validator_registration + ExecutionPayloadBidSigning: + allOf: + - $ref: '#/components/schemas/Signing' + - $ref: '#/components/schemas/ExecutionPayloadBidRequest' + - type: object + properties: + type: + type: "string" + description: Signing Request type + enum: + - 'EXECUTION_PAYLOAD_BID' + required: + - type + ExecutionPayloadBidRequest: + type: object + properties: + execution_payload_bid: + oneOf: + - $ref: '#/components/schemas/ExecutionPayloadBidRequestGloas' + discriminator: + propertyName: version + mapping: + GLOAS: '#/components/schemas/ExecutionPayloadBidRequestGloas' + required: + - execution_payload_bid + ExecutionPayloadBidRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify ExecutionPayloadBid request type.' + data: + $ref: "#/components/schemas/ExecutionPayloadBidGloas" + required: + - version + - data + ExecutionPayloadEnvelopeSigning: + allOf: + - $ref: '#/components/schemas/Signing' + - $ref: '#/components/schemas/ExecutionPayloadEnvelopeRequest' + - type: object + properties: + type: + type: "string" + description: Signing Request type + enum: + - 'EXECUTION_PAYLOAD_ENVELOPE' + required: + - type + ExecutionPayloadEnvelopeRequest: + type: object + properties: + execution_payload_envelope: + oneOf: + - $ref: '#/components/schemas/ExecutionPayloadEnvelopeRequestGloas' + discriminator: + propertyName: version + mapping: + GLOAS: '#/components/schemas/ExecutionPayloadEnvelopeRequestGloas' + required: + - execution_payload_envelope + ExecutionPayloadEnvelopeRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify ExecutionPayloadEnvelope request type.' + data: + $ref: "#/components/schemas/ExecutionPayloadEnvelopeGloas" + required: + - version + - data + PayloadAttestationMessageSigning: + allOf: + - $ref: '#/components/schemas/Signing' + - $ref: '#/components/schemas/PayloadAttestationMessageRequest' + - type: object + properties: + type: + type: "string" + description: Signing Request type + enum: + - 'PAYLOAD_ATTESTATION_MESSAGE' + required: + - type + PayloadAttestationMessageRequest: + type: object + properties: + payload_attestation_message: + oneOf: + - $ref: '#/components/schemas/PayloadAttestationMessageRequestGloas' + discriminator: + propertyName: version + mapping: + GLOAS: '#/components/schemas/PayloadAttestationMessageRequestGloas' + required: + - payload_attestation_message + PayloadAttestationMessageRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify PayloadAttestationMessage request type.' + data: + $ref: "#/components/schemas/PayloadAttestationDataGloas" + required: + - version + - data + ProposerPreferencesSigning: + allOf: + - $ref: '#/components/schemas/Signing' + - $ref: '#/components/schemas/ProposerPreferencesRequest' + - type: object + properties: + type: + type: "string" + description: Signing Request type + enum: + - 'PROPOSER_PREFERENCES' + required: + - type + ProposerPreferencesRequest: + type: object + properties: + proposer_preferences: + oneOf: + - $ref: '#/components/schemas/ProposerPreferencesRequestGloas' + discriminator: + propertyName: version + mapping: + GLOAS: '#/components/schemas/ProposerPreferencesRequestGloas' + required: + - proposer_preferences + ProposerPreferencesRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify ProposerPreferences request type.' + data: + $ref: "#/components/schemas/ProposerPreferencesGloas" + required: + - version + - data + BuilderRequestAuthSigning: + allOf: + - $ref: '#/components/schemas/BuilderRequestAuthRequest' + - type: object + properties: + type: + type: "string" + description: Signing Request type + enum: + - 'BUILDER_REQUEST_AUTH' + signingRoot: + type: "string" + description: 'signing root for optional verification if field present' + required: + - type + BuilderRequestAuthRequest: + type: object + properties: + builder_request_auth: + oneOf: + - $ref: '#/components/schemas/BuilderRequestAuthRequestGloas' + discriminator: + propertyName: version + mapping: + GLOAS: '#/components/schemas/BuilderRequestAuthRequestGloas' + required: + - builder_request_auth + BuilderRequestAuthRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify BuilderRequestAuth request type.' + data: + $ref: "#/components/schemas/BuilderRequestAuthGloas" + required: + - version + - data RandaoReveal: type: "object" properties: @@ -632,6 +839,294 @@ components: format: uint64 pubkey: type: string + ExecutionPayloadBidGloas: + type: object + description: >- + SSZ container signed by a builder using DOMAIN_BEACON_BUILDER (0x0B000000) + in Gloas for proposing an execution payload bid. + properties: + parent_block_hash: + type: string + description: Bytes32 hexadecimal + parent_block_root: + type: string + description: Bytes32 hexadecimal + block_hash: + type: string + description: Bytes32 hexadecimal + prev_randao: + type: string + description: Bytes32 hexadecimal + fee_recipient: + type: string + description: Bytes20 hexadecimal + gas_limit: + type: string + format: uint64 + builder_index: + type: string + format: uint64 + slot: + type: string + format: uint64 + value: + type: string + format: uint64 + execution_payment: + type: string + format: uint64 + blob_kzg_commitments: + type: array + items: + type: string + description: Bytes48 hexadecimal + execution_requests_root: + type: string + description: Bytes32 hexadecimal + ExecutionPayloadEnvelopeGloas: + type: object + description: >- + SSZ container signed by a builder, or by a proposer for self-builds, using + DOMAIN_BEACON_BUILDER (0x0B000000) in Gloas when revealing the actual execution + payload. + properties: + payload: + $ref: '#/components/schemas/ExecutionPayloadGloas' + execution_requests: + $ref: '#/components/schemas/ExecutionRequestsGloas' + builder_index: + type: string + format: uint64 + beacon_block_root: + type: string + description: Bytes32 hexadecimal + parent_beacon_block_root: + type: string + description: Bytes32 hexadecimal + ExecutionPayloadGloas: + type: object + description: >- + Gloas execution payload. Extends the Deneb execution payload with + block_access_list and slot_number fields. + properties: + parent_hash: + type: string + description: Bytes32 hexadecimal + fee_recipient: + type: string + description: Bytes20 hexadecimal + state_root: + type: string + description: Bytes32 hexadecimal + receipts_root: + type: string + description: Bytes32 hexadecimal + logs_bloom: + type: string + description: Bytes256 hexadecimal + prev_randao: + type: string + description: Bytes32 hexadecimal + block_number: + type: string + format: uint64 + gas_limit: + type: string + format: uint64 + gas_used: + type: string + format: uint64 + timestamp: + type: string + format: uint64 + extra_data: + type: string + description: SSZ hexadecimal + base_fee_per_gas: + type: string + description: UInt256 decimal + block_hash: + type: string + description: Bytes32 hexadecimal + transactions: + type: array + items: + type: string + description: SSZ hexadecimal + withdrawals: + type: array + description: List of validator withdrawals (Capella+). + items: + type: object + properties: + index: + type: string + format: uint64 + validator_index: + type: string + format: uint64 + address: + type: string + description: Bytes20 hexadecimal + amount: + type: string + format: uint64 + blob_gas_used: + type: string + format: uint64 + excess_blob_gas: + type: string + format: uint64 + block_access_list: + type: string + description: SSZ hexadecimal + slot_number: + type: string + format: uint64 + ExecutionRequestsGloas: + type: object + description: >- + Gloas execution requests. Extends the Electra execution requests + (deposits, withdrawals, consolidations) with builder_deposits and + builder_exits. + properties: + deposits: + type: array + items: + type: object + properties: + pubkey: + type: string + description: Bytes48 hexadecimal + withdrawal_credentials: + type: string + description: Bytes32 hexadecimal + amount: + type: string + format: uint64 + signature: + type: string + description: Bytes96 hexadecimal + index: + type: string + format: uint64 + withdrawals: + type: array + items: + type: object + properties: + source_address: + type: string + description: Bytes20 hexadecimal + validator_pubkey: + type: string + description: Bytes48 hexadecimal + amount: + type: string + format: uint64 + consolidations: + type: array + items: + type: object + properties: + source_address: + type: string + description: Bytes20 hexadecimal + source_pubkey: + type: string + description: Bytes48 hexadecimal + target_pubkey: + type: string + description: Bytes48 hexadecimal + builder_deposits: + type: array + items: + type: object + properties: + pubkey: + type: string + description: Bytes48 hexadecimal + withdrawal_credentials: + type: string + description: Bytes32 hexadecimal + amount: + type: string + format: uint64 + signature: + type: string + description: Bytes96 hexadecimal + builder_exits: + type: array + items: + type: object + properties: + source_address: + type: string + description: Bytes20 hexadecimal + pubkey: + type: string + description: Bytes48 hexadecimal + PayloadAttestationDataGloas: + type: object + description: >- + The data component of a PayloadAttestationMessage, signed by a PTC + validator using DOMAIN_PTC_ATTESTER (0x0C000000). + properties: + beacon_block_root: + type: string + description: Bytes32 hexadecimal + example: '0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2' + slot: + type: string + format: uint64 + example: '1' + payload_present: + type: boolean + description: Whether the execution payload for this slot is present + blob_data_available: + type: boolean + description: Whether blob data is available for this slot + ProposerPreferencesGloas: + type: object + description: >- + Container signed by a proposer using DOMAIN_PROPOSER_PREFERENCES + (0x0D000000) to advertise their preferences (fee_recipient, + target_gas_limit) for an upcoming proposal slot. + properties: + dependent_root: + type: string + description: Bytes32 hexadecimal + proposal_slot: + type: string + format: uint64 + validator_index: + type: string + format: uint64 + fee_recipient: + type: string + description: Bytes20 hexadecimal + target_gas_limit: + type: string + format: uint64 + BuilderRequestAuthGloas: + type: object + description: >- + Proposer-signed, out-of-protocol builder-API authentication message + (builder-specs, Gloas), used to authenticate per-request builder-API + calls (e.g. getExecutionPayloadBid, submitBuilderPreferences). Signed + with DOMAIN_BUILDER_REQUEST_AUTH (0x0B000001) computed from the genesis + fork version and a zero genesis_validators_root (no fork_info required), + not the proposer's current fork. + properties: + data: + type: string + description: >- + SSZ hexadecimal (max 4096 bytes, non-empty). Opaque authentication + data agreed with the builder out of band. + slot: + type: string + format: uint64 + description: The proposal slot this request is authorized for. BeaconBlockSigning: allOf: - $ref: '#/components/schemas/Signing' @@ -658,6 +1153,7 @@ components: - $ref: '#/components/schemas/BlockRequestDeneb' - $ref: '#/components/schemas/BlockRequestElectra' - $ref: '#/components/schemas/BlockRequestFulu' + - $ref: '#/components/schemas/BlockRequestGloas' discriminator: propertyName: version mapping: @@ -668,6 +1164,7 @@ components: DENEB: '#/components/schemas/BlockRequestDeneb' ELECTRA: '#/components/schemas/BlockRequestElectra' FULU: '#/components/schemas/BlockRequestFulu' + GLOAS: '#/components/schemas/BlockRequestGloas' required: - beacon_block BlockRequestPhase0: @@ -761,6 +1258,19 @@ components: required: - version - block_header + BlockRequestGloas: + type: object + properties: + version: + type: string + enum: + - GLOAS + description: 'version to identify block request type.' + block_header: + $ref: "#/components/schemas/BeaconBlockHeader" + required: + - version + - block_header BeaconBlockAltair: type: "object" properties: diff --git a/openapi-specs/eth2/web3signer.yaml b/openapi-specs/eth2/web3signer.yaml index 74a60ba17..f5ed2dd95 100644 --- a/openapi-specs/eth2/web3signer.yaml +++ b/openapi-specs/eth2/web3signer.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: 'Web3Signer ETH2 Api' description: 'Sign Eth2 Artifacts' - version: '2.0.0' + version: '2.1.0' license: name: 'Apache 2.0' url: 'http://www.apache.org/licenses/LICENSE-2.0.html' diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/metadata/parser/SigningMetadataModule.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/metadata/parser/SigningMetadataModule.java index ce7aee2b0..dbdc31035 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/metadata/parser/SigningMetadataModule.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/metadata/parser/SigningMetadataModule.java @@ -12,6 +12,7 @@ */ package tech.pegasys.web3signer.signing.config.metadata.parser; +import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -170,4 +171,19 @@ public UInt64 deserialize(JsonParser p, DeserializationContext ctxt) throws IOEx return UInt64.valueOf(p.getValueAsString()); } } + + public static class BLSPublicKeySerializer extends JsonSerializer { + @Override + public void serialize(BLSPublicKey value, JsonGenerator gen, SerializerProvider serializers) + throws IOException { + gen.writeString(value.toString()); + } + } + + public static class BLSPublicKeyDeserializer extends JsonDeserializer { + @Override + public BLSPublicKey deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + return BLSPublicKey.fromHexString(p.getValueAsString()); + } + } }