From 15fc5c5690751881d1311cb87a9485282b1d1460 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 2 Sep 2021 10:33:29 +1000 Subject: [PATCH] sign type BLOCK_v2 to support block signing for Altair and future forks (#422) * Introduce new sign type BLOCK_V2 which will allow Altair and future forks block signing requests. Keep BLOCK for phase 0 backward compatibility. * Upgrade Teku libraries to support Altair fork on Prater network. * Update OpenAPI spec. * Update Acceptance tests. * Add network to AT DSL. --- CHANGELOG.md | 10 ++ .../pegasys/web3signer/dsl/signer/Signer.java | 6 +- .../dsl/signer/SignerConfiguration.java | 9 +- .../signer/SignerConfigurationBuilder.java | 9 +- .../runner/CmdLineParamsConfigFileImpl.java | 5 + .../runner/CmdLineParamsDefaultImpl.java | 5 + .../utils/Eth2BlockSigningRequestUtil.java | 129 ++++++++++++++++++ .../dsl/utils/Eth2RequestUtils.java | 16 +++ .../filecoin/FcBlsSigningAcceptanceTest.java | 2 +- .../filecoin/FcSecpSigningAcceptanceTest.java | 2 +- .../signing/BlsSigningAcceptanceTest.java | 25 ++-- .../Eth2AltairBlockSigningAcceptanceTest.java | 89 ++++++++++++ .../Eth2DepositSigningAcceptanceTest.java | 3 +- .../signing/KeyLoadAndSignAcceptanceTest.java | 14 +- .../signing/SecpSigningAcceptanceTest.java | 7 +- .../signing/SigningAcceptanceTestBase.java | 37 ++++- build.gradle | 4 +- .../pegasys/web3signer/core/Eth2Runner.java | 10 +- .../core/service/http/ArtifactType.java | 1 + .../core/service/http/SigningJsonModule.java | 79 ----------- .../http/SigningObjectMapperFactory.java | 106 ++++++++++++++ .../handlers/signing/eth2/BlockRequest.java | 40 ++++++ .../eth2/Eth2SignForIdentifierHandler.java | 28 +++- .../signing/eth2/Eth2SigningRequestBody.java | 10 +- .../eth2/json/BlockRequestDeserializer.java | 53 +++++++ .../resources/openapi/web3signer-eth2.yaml | 71 +++++++++- .../allowed-licenses.json | 4 + gradle/versions.gradle | 4 +- 28 files changed, 640 insertions(+), 138 deletions(-) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2BlockSigningRequestUtil.java create mode 100644 acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AltairBlockSigningAcceptanceTest.java delete mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningJsonModule.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/json/BlockRequestDeserializer.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b77d6eed..5ddfdaf53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 21.8.1 + +### Features Added +- Added sign type BLOCK_V2 to support block signing for Phase0, Altair and future forks (Eth2 mode). BLOCK is not removed for +backward compatibility with PHASE0 blocks. +- Upgraded Teku libraries to 21.8.2 which added support for Altair upgrade on Prater testnet at epoch 36660. + +### Bugs fixed +- Unable to sign blocks on testnet Pyrmont after Altair fork. (Thanks to [Sephiroth](https://github.com/3eph1r0th) for reporting it.) + ## 21.8.0 ### Features Added diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/Signer.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/Signer.java index 08feb0964..a236b02b4 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/Signer.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/signer/Signer.java @@ -19,7 +19,7 @@ import static tech.pegasys.web3signer.dsl.utils.WaitUtils.waitFor; import static tech.pegasys.web3signer.tests.AcceptanceTestBase.JSON_RPC_PATH; -import tech.pegasys.web3signer.core.service.http.SigningJsonModule; +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SigningRequestBody; import tech.pegasys.web3signer.core.signing.KeyType; import tech.pegasys.web3signer.dsl.lotus.FilecoinJsonRpcEndpoint; @@ -59,9 +59,7 @@ public class Signer extends FilecoinJsonRpcEndpoint { public static final String RELOAD_ENDPOINT = "/reload"; public static final ObjectMapper ETH_2_INTERFACE_OBJECT_MAPPER = - new ObjectMapper() - .registerModule(new SigningJsonModule()) - .setSerializationInclusion(Include.NON_NULL); + SigningObjectMapperFactory.createObjectMapper().setSerializationInclusion(Include.NON_NULL); private static final String METRICS_ENDPOINT = "/metrics"; private final Web3SignerRunner runner; 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 dbd9a28ac..bee9b594d 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 @@ -55,6 +55,7 @@ public class SignerConfiguration { private final long slashingPruningSlotsPerEpoch; private final long slashingPruningInterval; private final Optional altairForkEpoch; + private final Optional network; public SignerConfiguration( final String hostname, @@ -84,7 +85,8 @@ public SignerConfiguration( final boolean swaggerUIEnabled, final boolean useConfigFile, final Optional slashingDbPoolConfigurationFile, - final Optional altairForkEpoch) { + final Optional altairForkEpoch, + final Optional network) { this.hostname = hostname; this.logLevel = logLevel; this.httpRpcPort = httpRpcPort; @@ -113,6 +115,7 @@ public SignerConfiguration( this.useConfigFile = useConfigFile; this.slashingProtectionDbPoolConfigurationFile = slashingDbPoolConfigurationFile; this.altairForkEpoch = altairForkEpoch; + this.network = network; } public String hostname() { @@ -234,4 +237,8 @@ public Optional getSlashingProtectionDbPoolConfigurationFile() { public Optional getAltairForkEpoch() { return altairForkEpoch; } + + 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 671cdae51..731cf6309 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 @@ -57,6 +57,7 @@ public class SignerConfigurationBuilder { private long slashingPruningSlotsPerEpoch = 1; private long slashingPruningInterval = 1; private Long altairForkEpoch = null; + private String network = null; public SignerConfigurationBuilder withLogLevel(final Level logLevel) { this.logLevel = logLevel; @@ -201,6 +202,11 @@ public SignerConfigurationBuilder withAltairForkEpoch(final long altairForkEpoch return this; } + public SignerConfigurationBuilder withNetwork(final String network) { + this.network = network; + return this; + } + public SignerConfiguration build() { if (mode == null) { throw new IllegalArgumentException("Mode cannot be null"); @@ -233,6 +239,7 @@ public SignerConfiguration build() { swaggerUIEnabled, useConfigFile, Optional.ofNullable(slashingProtectionDbPoolConfigurationFile), - Optional.ofNullable(altairForkEpoch)); + Optional.ofNullable(altairForkEpoch), + Optional.ofNullable(network)); } } 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 67db94261..689d275c4 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 @@ -255,6 +255,11 @@ private String createEth2SlashingProtectionArgs() { signerConfig.getAltairForkEpoch().get())); } + if (signerConfig.getNetwork().isPresent()) { + yamlConfig.append( + String.format(YAML_STRING_FMT, "eth2.network", signerConfig.getNetwork().get())); + } + return yamlConfig.toString(); } 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 e29328922..2183751ea 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 @@ -179,6 +179,11 @@ private Collection createEth2Args() { params.add(Long.toString(signerConfig.getAltairForkEpoch().get())); } + if (signerConfig.getNetwork().isPresent()) { + params.add("--network"); + params.add(signerConfig.getNetwork().get()); + } + return params; } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2BlockSigningRequestUtil.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2BlockSigningRequestUtil.java new file mode 100644 index 000000000..12fe2c3c0 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/Eth2BlockSigningRequestUtil.java @@ -0,0 +1,129 @@ +/* + * Copyright 2021 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.dsl.utils; + +import tech.pegasys.teku.api.schema.Fork; +import tech.pegasys.teku.api.schema.altair.BeaconBlockAltair; +import tech.pegasys.teku.api.schema.altair.BeaconBlockBodyAltair; +import tech.pegasys.teku.core.signatures.SigningRootUtil; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.web3signer.core.service.http.ArtifactType; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.BlockRequest; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SigningRequestBody; + +import org.apache.tuweni.bytes.Bytes; + +public class Eth2BlockSigningRequestUtil { + private final SpecMilestone specMilestone; + private final DataStructureUtil dataStructureUtil; + private final SigningRootUtil signingRootUtil; + private final ForkInfo tekuForkInfo; + private final Fork tekuFork; + private final tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ForkInfo forkInfo; + private final BeaconBlock beaconBlock; + private final Bytes signingRoot; + + public Eth2BlockSigningRequestUtil(final SpecMilestone specMilestone) { + final Spec spec; + switch (specMilestone) { + case ALTAIR: + spec = TestSpecFactory.createMinimalAltair(); + break; + case PHASE0: + spec = TestSpecFactory.createMinimalPhase0(); + break; + default: + throw new IllegalStateException("Spec Milestone not yet supported: " + specMilestone); + } + this.specMilestone = specMilestone; + dataStructureUtil = new DataStructureUtil(spec); + signingRootUtil = new SigningRootUtil(spec); + tekuForkInfo = dataStructureUtil.randomForkInfo(); + tekuFork = new Fork(tekuForkInfo.getFork()); + forkInfo = + new tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.ForkInfo( + tekuFork, tekuForkInfo.getGenesisValidatorsRoot()); + beaconBlock = dataStructureUtil.randomBeaconBlock(0); + signingRoot = signingRootUtil.signingRootForSignBlock(beaconBlock, tekuForkInfo); + } + + public Eth2SigningRequestBody createBlockV2Request() { + final BlockRequest blockRequest = new BlockRequest(specMilestone, getBeaconBlock()); + + return new Eth2SigningRequestBody( + ArtifactType.BLOCK_V2, + signingRoot, + forkInfo, + null, + blockRequest, + null, + null, + null, + null, + null, + null, + null, + null, + null); + } + + public Eth2SigningRequestBody createLegacyBlockRequest() { + if (specMilestone != SpecMilestone.PHASE0) { + throw new IllegalStateException( + "Only PHASE0 spec is supported to create legacy BLOCK type signing request"); + } + + return new Eth2SigningRequestBody( + ArtifactType.BLOCK, + signingRoot, + forkInfo, + getBeaconBlock(), + null, + null, + null, + null, + null, + null, + null, + null, + null, + null); + } + + private tech.pegasys.teku.api.schema.BeaconBlock getBeaconBlock() { + if (specMilestone == SpecMilestone.ALTAIR) { + return new BeaconBlockAltair( + beaconBlock.getSlot(), + beaconBlock.getProposerIndex(), + beaconBlock.getParentRoot(), + beaconBlock.getStateRoot(), + getBeaconBlockBodyAltair(beaconBlock.getBody())); + } else if (specMilestone == SpecMilestone.PHASE0) { + return new tech.pegasys.teku.api.schema.BeaconBlock(beaconBlock); + } + + throw new IllegalStateException("Spec milestone not yet supported: " + specMilestone); + } + + private BeaconBlockBodyAltair getBeaconBlockBodyAltair( + final tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody body) { + return new BeaconBlockBodyAltair( + tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.BeaconBlockBodyAltair + .required(body)); + } +} 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 88a4a2902..1f10a1a4f 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 @@ -31,6 +31,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecFactory; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.ContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SyncAggregatorSelectionData; @@ -78,6 +79,9 @@ public class Eth2RequestUtils { syncCommitteeUtil.createContributionAndProof( UInt64.valueOf(11), contribution, aggregatorSignature); + private static final Eth2BlockSigningRequestUtil ALTAIR_BLOCK_UTIL = + new Eth2BlockSigningRequestUtil(SpecMilestone.ALTAIR); + public static Eth2SigningRequestBody createCannedRequest(final ArtifactType artifactType) { switch (artifactType) { case DEPOSIT: @@ -88,6 +92,8 @@ public static Eth2SigningRequestBody createCannedRequest(final ArtifactType arti return createRandaoReveal(); case BLOCK: return createBlockRequest(); + case BLOCK_V2: + return ALTAIR_BLOCK_UTIL.createBlockV2Request(); case ATTESTATION: return createAttestationRequest(); case AGGREGATION_SLOT: @@ -139,6 +145,7 @@ private static Eth2SigningRequestBody createAggregateAndProof() { null, null, null, + null, aggregateAndProof, null, null, @@ -160,6 +167,7 @@ private static Eth2SigningRequestBody createAggregationSlot() { forkInfo, null, null, + null, aggregationSlot, null, null, @@ -189,6 +197,7 @@ private static Eth2SigningRequestBody createRandaoReveal() { null, null, null, + null, randaoReveal, null, null, @@ -210,6 +219,7 @@ private static Eth2SigningRequestBody createVoluntaryExit() { null, null, null, + null, voluntaryExit, null, null, @@ -241,6 +251,7 @@ private static Eth2SigningRequestBody createDepositRequest() { null, null, null, + null, depositMessage, null, null, @@ -269,6 +280,7 @@ public static Eth2SigningRequestBody createAttestationRequest( signingRoot, forkInfo, null, + null, attestationData, null, null, @@ -327,6 +339,7 @@ public static Eth2SigningRequestBody createBlockRequest( null, null, null, + null, null); } @@ -371,6 +384,7 @@ private static Eth2SigningRequestBody createSyncCommitteeMessageRequest() { null, null, null, + null, syncCommitteeMessage, null, null); @@ -407,6 +421,7 @@ private static Eth2SigningRequestBody createSyncCommitteeSelectionProofRequest() null, null, null, + null, getSyncAggregatorSelectionData(slot, subcommitteeIndex), null); } @@ -446,6 +461,7 @@ private static Eth2SigningRequestBody createSyncCommitteeContributionAndProofReq null, null, null, + null, getContributionAndProof()); } diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcBlsSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcBlsSigningAcceptanceTest.java index d8b550cc6..8a41288af 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcBlsSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcBlsSigningAcceptanceTest.java @@ -68,7 +68,7 @@ void receiveASignatureWhenSubmitSigningRequestToFilecoinEndpoint() { final String configFilename = publicKey.toString().substring(2); final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - setupSigner("filecoin"); + setupFilecoinSigner(); final ValueNode id = JsonNodeFactory.instance.numberNode(1); final ObjectMapper mapper = new ObjectMapper(); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcSecpSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcSecpSigningAcceptanceTest.java index dae746953..7c11db128 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcSecpSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/filecoin/FcSecpSigningAcceptanceTest.java @@ -64,7 +64,7 @@ void receiveASignatureWhenSubmitSigningRequestToFilecoinEndpoint() throws URISyn metadataFileHelpers.createKeyStoreYamlFileAt( keyConfigFile, Path.of(keyPath), "pass", KeyType.SECP256K1); - setupSigner("filecoin"); + setupFilecoinSigner(); final ValueNode id = JsonNodeFactory.instance.numberNode(1); final ObjectMapper mapper = new ObjectMapper(); 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 bf9acac84..371bafc77 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 @@ -33,7 +33,6 @@ import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers; import java.nio.file.Path; -import java.util.Map; import com.fasterxml.jackson.core.JsonProcessingException; import io.restassured.http.ContentType; @@ -66,7 +65,7 @@ public void signDataWithKeyLoadedFromUnencryptedFile(final ArtifactType artifact final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - signAndVerifySignature(artifactType, TEXT, null); + signAndVerifySignature(artifactType, TEXT); } @ParameterizedTest @@ -77,7 +76,7 @@ public void signDataWithJsonAcceptTypeWithKeyLoadedFromUnencryptedFile( final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - signAndVerifySignature(artifactType, JSON, null); + signAndVerifySignature(artifactType, JSON); } @ParameterizedTest @@ -88,7 +87,7 @@ public void signDataWithDefaultAcceptTypeWithKeyLoadedFromUnencryptedFile( final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); // this is same as not setting accept type at all - the client defaults to */* aka ANY - signAndVerifySignature(artifactType, ANY, null); + signAndVerifySignature(artifactType, ANY); } @ParameterizedTest @@ -155,7 +154,7 @@ public void failsIfSigningRootDoesNotMatchSigningData(final ArtifactType artifac final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createKeyStoreYamlFileAt(keyConfigFile, keyPair, KdfFunction.SCRYPT); - setupSigner("eth2", null); + setupEth2Signer(); final Eth2SigningRequestBody request = Eth2RequestUtils.createCannedRequest(artifactType); final Eth2SigningRequestBody requestWithMismatchedSigningRoot = @@ -164,6 +163,7 @@ public void failsIfSigningRootDoesNotMatchSigningData(final ArtifactType artifac Bytes32.ZERO, request.getForkInfo(), request.getBlock(), + request.getBlockRequest(), request.getAttestation(), request.getAggregationSlot(), request.getAggregateAndProof(), @@ -190,7 +190,7 @@ public void ableToSignWithoutSigningRootField(final ContentType acceptableConten final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); metadataFileHelpers.createKeyStoreYamlFileAt(keyConfigFile, keyPair, KdfFunction.SCRYPT); - setupSigner("eth2", null); + setupEth2Signer(); final Eth2SigningRequestBody request = Eth2RequestUtils.createBlockRequest(); @@ -200,6 +200,7 @@ public void ableToSignWithoutSigningRootField(final ContentType acceptableConten null, request.getForkInfo(), request.getBlock(), + request.getBlockRequest(), request.getAttestation(), request.getAggregationSlot(), request.getAggregateAndProof(), @@ -231,15 +232,17 @@ public void ableToSignWithoutSigningRootField(final ContentType acceptableConten private void signAndVerifySignature(final ArtifactType artifactType) throws JsonProcessingException { - signAndVerifySignature(artifactType, TEXT, null); + signAndVerifySignature(artifactType, TEXT); } private void signAndVerifySignature( - final ArtifactType artifactType, - final ContentType acceptMediaType, - final Map env) + final ArtifactType artifactType, final ContentType acceptMediaType) throws JsonProcessingException { - setupSigner("eth2", env); + if (artifactType == ArtifactType.BLOCK_V2) { + setupEth2SignerMinimal(); + } else { + setupEth2Signer(); + } // openapi final Eth2SigningRequestBody request = Eth2RequestUtils.createCannedRequest(artifactType); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AltairBlockSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AltairBlockSigningAcceptanceTest.java new file mode 100644 index 000000000..82a479aa4 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2AltairBlockSigningAcceptanceTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 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.tests.signing; + +import static org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.bls.BLSSecretKey; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SigningRequestBody; +import tech.pegasys.web3signer.core.signing.KeyType; +import tech.pegasys.web3signer.dsl.utils.Eth2BlockSigningRequestUtil; +import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers; + +import java.nio.file.Path; + +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class Eth2AltairBlockSigningAcceptanceTest extends SigningAcceptanceTestBase { + private static final String PRIVATE_KEY = + "3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35"; + + private static final MetadataFileHelpers metadataFileHelpers = new MetadataFileHelpers(); + private static final BLSSecretKey key = + BLSSecretKey.fromBytes(Bytes32.fromHexString(PRIVATE_KEY)); + private static final BLSKeyPair keyPair = new BLSKeyPair(key); + private static final BLSPublicKey publicKey = keyPair.getPublicKey(); + + @BeforeEach + void setup() { + final String configFilename = publicKey.toString().substring(2); + final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); + metadataFileHelpers.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); + } + + @ParameterizedTest + @EnumSource + void signAndVerifyBlockV2Signature(final SpecMilestone specMilestone) throws Exception { + final Eth2BlockSigningRequestUtil util = new Eth2BlockSigningRequestUtil(specMilestone); + + if (specMilestone == SpecMilestone.ALTAIR) { + setupEth2SignerMinimal(); + } else { + setupEth2SignerMinimalWithoutAltairFork(); + } + + final Eth2SigningRequestBody request = util.createBlockV2Request(); + final Response response = + signer.eth2Sign(keyPair.getPublicKey().toString(), request, ContentType.JSON); + final Bytes signature = verifyAndGetSignatureResponse(response, ContentType.JSON); + final BLSSignature expectedSignature = + BLS.sign(keyPair.getSecretKey(), request.getSigningRoot()); + assertThat(signature).isEqualTo(expectedSignature.toBytesCompressed()); + } + + @Test + void signAndVerifyLegacyBlockSignature() throws Exception { + final Eth2BlockSigningRequestUtil util = new Eth2BlockSigningRequestUtil(SpecMilestone.PHASE0); + setupEth2SignerMinimalWithoutAltairFork(); + + final Eth2SigningRequestBody request = util.createLegacyBlockRequest(); + final Response response = + signer.eth2Sign(keyPair.getPublicKey().toString(), request, ContentType.JSON); + final Bytes signature = verifyAndGetSignatureResponse(response, ContentType.JSON); + final BLSSignature expectedSignature = + BLS.sign(keyPair.getSecretKey(), request.getSigningRoot()); + assertThat(signature).isEqualTo(expectedSignature.toBytesCompressed()); + } +} diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2DepositSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2DepositSigningAcceptanceTest.java index 80995540e..70c581245 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2DepositSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/Eth2DepositSigningAcceptanceTest.java @@ -55,7 +55,7 @@ List signDepositData() { metadataFileHelpers.createUnencryptedYamlFileAt( testDirectory.resolve("2.yaml"), PRIVATE_KEY2, BLS); - setupSigner("eth2"); + setupEth2Signer(); final ObjectMapper objectMapper = new ObjectMapper(); @@ -109,6 +109,7 @@ private void verifyDepositData(final Map depositData) throws IOE null, null, null, + null, depositMessage, null, null, diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/KeyLoadAndSignAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/KeyLoadAndSignAcceptanceTest.java index 86289897e..bbcab3126 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/KeyLoadAndSignAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/KeyLoadAndSignAcceptanceTest.java @@ -56,7 +56,11 @@ public class KeyLoadAndSignAcceptanceTest extends SigningAcceptanceTestBase { @EnumSource(value = KeyType.class) public void receiveA404IfRequestedKeyDoesNotExist(final KeyType keyType) throws JsonProcessingException { - setupSigner(keyType == KeyType.BLS ? "eth2" : "eth1"); + if (keyType == KeyType.BLS) { + setupEth1Signer(); + } else { + setupEth2Signer(); + } final String body = createBody(keyType); given() .baseUri(signer.getUrl()) @@ -78,7 +82,7 @@ public void receiveA400IfDataIsNotValid(final String data) { final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); METADATA_FILE_HELPERS.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - setupSigner("eth2"); + setupEth2Signer(); // without client-side openapi validator given() @@ -99,7 +103,7 @@ public void receiveA400IfDataIsMissingFromJsonBody() { final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); METADATA_FILE_HELPERS.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - setupSigner("eth2"); + setupEth2Signer(); // without OpenAPI validation filter given() @@ -120,7 +124,7 @@ public void receiveA400IfJsonBodyIsMalformed() { final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); METADATA_FILE_HELPERS.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - setupSigner("eth2"); + setupEth2Signer(); // without OpenAPI validation filter given() @@ -141,7 +145,7 @@ public void unusedFieldsInRequestDoesNotAffectSigning() throws JsonProcessingExc final Path keyConfigFile = testDirectory.resolve(configFilename + ".yaml"); METADATA_FILE_HELPERS.createUnencryptedYamlFileAt(keyConfigFile, PRIVATE_KEY, KeyType.BLS); - setupSigner("eth2"); + setupEth2Signer(); final Eth2SigningRequestBody blockRequest = Eth2RequestUtils.createBlockRequest(); final JsonObject jsonObject = diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java index 75b307a42..334338105 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/signing/SecpSigningAcceptanceTest.java @@ -28,7 +28,6 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.security.SignatureException; -import java.util.Map; import com.google.common.io.Resources; import io.restassured.response.Response; @@ -126,11 +125,7 @@ public void signDatWithKeyFromAzure(@TempDir Path keyConfigDirectory) { } private void signAndVerifySignature() { - signAndVerifySignature(null); - } - - private void signAndVerifySignature(final Map env) { - setupSigner("eth1", env); + setupEth1Signer(); // openapi final Response response = signer.eth1Sign(PUBLIC_KEY_HEX_STRING, DATA); 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 fccc0a647..b2a9236cd 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 @@ -16,7 +16,6 @@ import tech.pegasys.web3signer.tests.AcceptanceTestBase; import java.nio.file.Path; -import java.util.Map; import io.restassured.http.ContentType; import io.restassured.response.Response; @@ -25,18 +24,42 @@ public class SigningAcceptanceTestBase extends AcceptanceTestBase { protected @TempDir Path testDirectory; + private static final Long MINIMAL_ALTAIR_FORK = 0L; - protected void setupSigner(final String mode) { - setupSigner(mode, null); + protected void setupEth1Signer() { + final SignerConfigurationBuilder builder = new SignerConfigurationBuilder(); + builder.withKeyStoreDirectory(testDirectory).withMode("eth1"); + startSigner(builder.build()); + } + + protected void setupFilecoinSigner() { + final SignerConfigurationBuilder builder = new SignerConfigurationBuilder(); + builder.withKeyStoreDirectory(testDirectory).withMode("filecoin"); + startSigner(builder.build()); + } + + protected void setupEth2Signer() { + final SignerConfigurationBuilder builder = new SignerConfigurationBuilder(); + builder + .withKeyStoreDirectory(testDirectory) + .withMode("eth2") + .withAltairForkEpoch(MINIMAL_ALTAIR_FORK); + startSigner(builder.build()); } - protected void setupSigner(final String mode, final Map env) { + protected void setupEth2SignerMinimal() { final SignerConfigurationBuilder builder = new SignerConfigurationBuilder(); builder .withKeyStoreDirectory(testDirectory) - .withMode(mode) - .withAltairForkEpoch(0) - .withEnvironment(env); + .withMode("eth2") + .withNetwork("minimal") + .withAltairForkEpoch(MINIMAL_ALTAIR_FORK); + startSigner(builder.build()); + } + + protected void setupEth2SignerMinimalWithoutAltairFork() { + final SignerConfigurationBuilder builder = new SignerConfigurationBuilder(); + builder.withKeyStoreDirectory(testDirectory).withMode("eth2").withNetwork("minimal"); startSigner(builder.build()); } diff --git a/build.gradle b/build.gradle index 5c1204d61..9fb0e5382 100644 --- a/build.gradle +++ b/build.gradle @@ -113,10 +113,10 @@ allprojects { repositories { jcenter() mavenCentral() - maven { url "https://hyperledger.jfrog.io/artifactory/besu-maven" } - maven { url "https://hyperledger-org.bintray.com/besu-repo/" } maven { url "https://artifacts.consensys.net/public/maven/maven/" } maven { url "https://artifacts.consensys.net/public/teku/maven/" } + maven { url "https://hyperledger.jfrog.io/artifactory/besu-maven" } + maven { url "https://hyperledger-org.bintray.com/besu-repo/" } maven { url "https://jitpack.io" } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java index dddece1ce..733977e0b 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -33,7 +33,7 @@ import tech.pegasys.web3signer.core.multikey.metadata.interlock.InterlockKeyProvider; import tech.pegasys.web3signer.core.multikey.metadata.parser.YamlSignerParser; import tech.pegasys.web3signer.core.multikey.metadata.yubihsm.YubiHsmOpaqueDataProvider; -import tech.pegasys.web3signer.core.service.http.SigningJsonModule; +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.core.service.http.handlers.LogErrorHandler; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SignForIdentifierHandler; @@ -53,8 +53,6 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import io.vertx.core.Vertx; @@ -126,11 +124,7 @@ private void registerEth2Routes( final LogErrorHandler errorHandler, final MetricsSystem metricsSystem, final Optional slashingProtection) { - final ObjectMapper objectMapper = - new ObjectMapper() - .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) - .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - .registerModule(new SigningJsonModule()); + final ObjectMapper objectMapper = SigningObjectMapperFactory.createObjectMapper(); addPublicKeysListHandler(routerFactory, blsSignerProvider, ETH2_LIST.name(), errorHandler); 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 4880dfa8e..1e3380c53 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 @@ -14,6 +14,7 @@ public enum ArtifactType { BLOCK, + BLOCK_V2, ATTESTATION, AGGREGATION_SLOT, AGGREGATE_AND_PROOF, diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningJsonModule.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningJsonModule.java deleted file mode 100644 index fc1c1fc20..000000000 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningJsonModule.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 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; - -import tech.pegasys.teku.api.schema.BLSPubKey; -import tech.pegasys.teku.api.schema.BLSSignature; -import tech.pegasys.teku.provider.BLSPubKeyDeserializer; -import tech.pegasys.teku.provider.BLSPubKeySerializer; -import tech.pegasys.teku.provider.BLSSignatureDeserializer; -import tech.pegasys.teku.provider.BLSSignatureSerializer; -import tech.pegasys.teku.provider.Bytes32Deserializer; -import tech.pegasys.teku.provider.Bytes4Deserializer; -import tech.pegasys.teku.provider.Bytes4Serializer; -import tech.pegasys.teku.provider.BytesDeserializer; -import tech.pegasys.teku.provider.BytesSerializer; -import tech.pegasys.teku.provider.DoubleDeserializer; -import tech.pegasys.teku.provider.DoubleSerializer; -import tech.pegasys.teku.provider.SszBitlistDeserializer; -import tech.pegasys.teku.provider.SszBitlistSerializer; -import tech.pegasys.teku.provider.SszBitvectorDeserializer; -import tech.pegasys.teku.provider.SszBitvectorSerializer; -import tech.pegasys.teku.provider.UInt64Deserializer; -import tech.pegasys.teku.provider.UInt64Serializer; -import tech.pegasys.teku.ssz.collections.SszBitlist; -import tech.pegasys.teku.ssz.collections.SszBitvector; -import tech.pegasys.teku.ssz.type.Bytes4; -import tech.pegasys.web3signer.common.JacksonSerializers.HexDeserialiser; -import tech.pegasys.web3signer.common.JacksonSerializers.HexSerialiser; -import tech.pegasys.web3signer.common.JacksonSerializers.StringUInt64Deserializer; -import tech.pegasys.web3signer.common.JacksonSerializers.StringUInt64Serialiser; -import tech.pegasys.web3signer.core.multikey.metadata.parser.SigningMetadataModule.Bytes32Serializer; - -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.UInt64; - -public class SigningJsonModule extends SimpleModule { - - public SigningJsonModule() { - super("SigningJsonRpcModule"); - addDeserializer(Bytes.class, new HexDeserialiser()); - addSerializer(Bytes.class, new HexSerialiser()); - addDeserializer(UInt64.class, new StringUInt64Deserializer()); - addSerializer(UInt64.class, new StringUInt64Serialiser()); - addDeserializer( - tech.pegasys.teku.infrastructure.unsigned.UInt64.class, new UInt64Deserializer()); - addSerializer(tech.pegasys.teku.infrastructure.unsigned.UInt64.class, new UInt64Serializer()); - - addSerializer(Bytes32.class, new Bytes32Serializer()); - addDeserializer(Bytes32.class, new Bytes32Deserializer()); - addDeserializer(Bytes4.class, new Bytes4Deserializer()); - addSerializer(Bytes4.class, new Bytes4Serializer()); - addDeserializer(Bytes.class, new BytesDeserializer()); - addSerializer(Bytes.class, new BytesSerializer()); - addDeserializer(Double.class, new DoubleDeserializer()); - addSerializer(Double.class, new DoubleSerializer()); - - addSerializer(BLSPubKey.class, new BLSPubKeySerializer()); - addDeserializer(BLSPubKey.class, new BLSPubKeyDeserializer()); - addDeserializer(BLSSignature.class, new BLSSignatureDeserializer()); - addSerializer(BLSSignature.class, new BLSSignatureSerializer()); - - addSerializer(SszBitlist.class, new SszBitlistSerializer()); - addDeserializer(SszBitlist.class, new SszBitlistDeserializer()); - addDeserializer(SszBitvector.class, new SszBitvectorDeserializer()); - addSerializer(SszBitvector.class, new SszBitvectorSerializer()); - } -} 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 new file mode 100644 index 000000000..9185e0c70 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/SigningObjectMapperFactory.java @@ -0,0 +1,106 @@ +/* + * Copyright 2020 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; + +import tech.pegasys.teku.api.schema.BLSPubKey; +import tech.pegasys.teku.api.schema.BLSSignature; +import tech.pegasys.teku.provider.BLSPubKeyDeserializer; +import tech.pegasys.teku.provider.BLSPubKeySerializer; +import tech.pegasys.teku.provider.BLSSignatureDeserializer; +import tech.pegasys.teku.provider.BLSSignatureSerializer; +import tech.pegasys.teku.provider.Bytes32Deserializer; +import tech.pegasys.teku.provider.Bytes4Deserializer; +import tech.pegasys.teku.provider.Bytes4Serializer; +import tech.pegasys.teku.provider.BytesDeserializer; +import tech.pegasys.teku.provider.BytesSerializer; +import tech.pegasys.teku.provider.DoubleDeserializer; +import tech.pegasys.teku.provider.DoubleSerializer; +import tech.pegasys.teku.provider.SszBitlistDeserializer; +import tech.pegasys.teku.provider.SszBitlistSerializer; +import tech.pegasys.teku.provider.SszBitvectorDeserializer; +import tech.pegasys.teku.provider.SszBitvectorSerializer; +import tech.pegasys.teku.provider.UInt64Deserializer; +import tech.pegasys.teku.provider.UInt64Serializer; +import tech.pegasys.teku.ssz.collections.SszBitlist; +import tech.pegasys.teku.ssz.collections.SszBitvector; +import tech.pegasys.teku.ssz.type.Bytes4; +import tech.pegasys.web3signer.common.JacksonSerializers.HexDeserialiser; +import tech.pegasys.web3signer.common.JacksonSerializers.HexSerialiser; +import tech.pegasys.web3signer.common.JacksonSerializers.StringUInt64Deserializer; +import tech.pegasys.web3signer.common.JacksonSerializers.StringUInt64Serialiser; +import tech.pegasys.web3signer.core.multikey.metadata.parser.SigningMetadataModule.Bytes32Serializer; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.BlockRequest; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.json.BlockRequestDeserializer; + +import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +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.UInt64; + +public class SigningObjectMapperFactory { + private final ObjectMapper objectMapper; + + private static final SigningObjectMapperFactory factory = new SigningObjectMapperFactory(); + + private SigningObjectMapperFactory() { + this.objectMapper = + new ObjectMapper() + .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + addWeb3SignerMappers(); + } + + private void addWeb3SignerMappers() { + final SimpleModule module = + new SimpleModule("SigningJsonRpcModule", new Version(1, 0, 0, null, null, null)); + module.addDeserializer(Bytes.class, new HexDeserialiser()); + module.addSerializer(Bytes.class, new HexSerialiser()); + module.addDeserializer(UInt64.class, new StringUInt64Deserializer()); + module.addSerializer(UInt64.class, new StringUInt64Serialiser()); + module.addDeserializer( + tech.pegasys.teku.infrastructure.unsigned.UInt64.class, new UInt64Deserializer()); + module.addSerializer( + tech.pegasys.teku.infrastructure.unsigned.UInt64.class, new UInt64Serializer()); + + module.addSerializer(Bytes32.class, new Bytes32Serializer()); + module.addDeserializer(Bytes32.class, new Bytes32Deserializer()); + module.addDeserializer(Bytes4.class, new Bytes4Deserializer()); + module.addSerializer(Bytes4.class, new Bytes4Serializer()); + module.addDeserializer(Bytes.class, new BytesDeserializer()); + module.addSerializer(Bytes.class, new BytesSerializer()); + module.addDeserializer(Double.class, new DoubleDeserializer()); + module.addSerializer(Double.class, new DoubleSerializer()); + + module.addSerializer(BLSPubKey.class, new BLSPubKeySerializer()); + module.addDeserializer(BLSPubKey.class, new BLSPubKeyDeserializer()); + module.addDeserializer(BLSSignature.class, new BLSSignatureDeserializer()); + module.addSerializer(BLSSignature.class, new BLSSignatureSerializer()); + + module.addSerializer(SszBitlist.class, new SszBitlistSerializer()); + module.addDeserializer(SszBitlist.class, new SszBitlistDeserializer()); + module.addDeserializer(SszBitvector.class, new SszBitvectorDeserializer()); + module.addSerializer(SszBitvector.class, new SszBitvectorSerializer()); + + module.addDeserializer(BlockRequest.class, new BlockRequestDeserializer(objectMapper)); + + objectMapper.registerModule(module); + } + + public static ObjectMapper createObjectMapper() { + return factory.objectMapper; + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java new file mode 100644 index 000000000..524e8a990 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/BlockRequest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2020 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.api.schema.BeaconBlock; +import tech.pegasys.teku.spec.SpecMilestone; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BlockRequest { + private final SpecMilestone version; + private final BeaconBlock beaconBlock; + + public BlockRequest( + @JsonProperty("version") final SpecMilestone version, + @JsonProperty("block") final BeaconBlock beaconBlock) { + this.version = version; + this.beaconBlock = beaconBlock; + } + + @JsonProperty("version") + public SpecMilestone getVersion() { + return version; + } + + @JsonProperty("block") + public BeaconBlock getBeaconBlock() { + return beaconBlock; + } +} 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 de217c165..895dc409e 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 @@ -175,11 +175,13 @@ private boolean maySign( final ForkInfo forkInfo = eth2SigningRequestBody.getForkInfo(); switch (eth2SigningRequestBody.getType()) { case BLOCK: - final BeaconBlock beaconBlock = eth2SigningRequestBody.getBlock(); - final UInt64 blockSlot = UInt64.valueOf(beaconBlock.slot.bigIntegerValue()); - return slashingProtection - .get() - .maySignBlock(publicKey, signingRoot, blockSlot, forkInfo.getGenesisValidatorsRoot()); + return maySignBlock(publicKey, signingRoot, eth2SigningRequestBody.getBlock(), forkInfo); + case BLOCK_V2: + return maySignBlock( + publicKey, + signingRoot, + eth2SigningRequestBody.getBlockRequest().getBeaconBlock(), + forkInfo); case ATTESTATION: final AttestationData attestation = eth2SigningRequestBody.getAttestation(); return slashingProtection @@ -195,6 +197,17 @@ private boolean maySign( } } + private boolean maySignBlock( + final Bytes publicKey, + final Bytes signingRoot, + final BeaconBlock beaconBlock, + final ForkInfo forkInfo) { + final UInt64 blockSlot = UInt64.valueOf(beaconBlock.slot.bigIntegerValue()); + return slashingProtection + .get() + .maySignBlock(publicKey, signingRoot, blockSlot, forkInfo.getGenesisValidatorsRoot()); + } + private Bytes computeSigningRoot(final Eth2SigningRequestBody body) { switch (body.getType()) { case BLOCK: @@ -202,6 +215,11 @@ private Bytes computeSigningRoot(final Eth2SigningRequestBody body) { return signingRootUtil.signingRootForSignBlock( body.getBlock().asInternalBeaconBlock(eth2Spec), body.getForkInfo().asInternalForkInfo()); + case BLOCK_V2: + checkArgument(body.getBlockRequest() != null, "beacon_block must be specified"); + return signingRootUtil.signingRootForSignBlock( + body.getBlockRequest().getBeaconBlock().asInternalBeaconBlock(eth2Spec), + body.getForkInfo().asInternalForkInfo()); case ATTESTATION: checkArgument(body.getAttestation() != null, "attestation must be specified"); return signingRootUtil.signingRootForSignAttestationData( 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 7a4975b35..dde399ea6 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 @@ -27,7 +27,8 @@ public class Eth2SigningRequestBody { private final ArtifactType type; private final Bytes signingRoot; private final ForkInfo fork_info; - private final BeaconBlock beaconBlock; + private final BeaconBlock beaconBlock; // phase 0 + private final BlockRequest blockRequest; // altair and onward private final AttestationData attestation; private final AggregationSlot aggregation_slot; private final AggregateAndProof aggregate_and_proof; @@ -44,6 +45,7 @@ public Eth2SigningRequestBody( @JsonProperty("signingRoot") final Bytes signingRoot, @JsonProperty("fork_info") final ForkInfo fork_info, @JsonProperty("block") final BeaconBlock block, + @JsonProperty("beacon_block") final BlockRequest blockRequest, @JsonProperty("attestation") final AttestationData attestation, @JsonProperty("aggregation_slot") final AggregationSlot aggregation_slot, @JsonProperty("aggregate_and_proof") final AggregateAndProof aggregate_and_proof, @@ -58,6 +60,7 @@ public Eth2SigningRequestBody( this.signingRoot = signingRoot; this.fork_info = fork_info; this.beaconBlock = block; + this.blockRequest = blockRequest; this.attestation = attestation; this.aggregation_slot = aggregation_slot; this.aggregate_and_proof = aggregate_and_proof; @@ -84,6 +87,11 @@ public BeaconBlock getBlock() { return beaconBlock; } + @JsonProperty("beacon_block") + public BlockRequest getBlockRequest() { + return blockRequest; + } + @JsonProperty("attestation") public AttestationData getAttestation() { return attestation; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/json/BlockRequestDeserializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/json/BlockRequestDeserializer.java new file mode 100644 index 000000000..88e62756a --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/eth2/json/BlockRequestDeserializer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 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.json; + +import tech.pegasys.teku.api.schema.BeaconBlock; +import tech.pegasys.teku.api.schema.altair.BeaconBlockAltair; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.BlockRequest; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class BlockRequestDeserializer extends JsonDeserializer { + private final ObjectMapper objectMapper; + + public BlockRequestDeserializer(final ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + @Override + public BlockRequest deserialize(final JsonParser p, final DeserializationContext ctxt) + throws IOException { + final JsonNode node = p.getCodec().readTree(p); + final SpecMilestone specMilestone = SpecMilestone.valueOf(node.findValue("version").asText()); + final BeaconBlock beaconBlock; + switch (specMilestone) { + case ALTAIR: + beaconBlock = objectMapper.treeToValue(node.findValue("block"), BeaconBlockAltair.class); + break; + case PHASE0: + beaconBlock = objectMapper.treeToValue(node.findValue("block"), BeaconBlock.class); + break; + default: + throw new IOException("Unsupported Milestone during deserialization: " + specMilestone); + } + return new BlockRequest(specMilestone, beaconBlock); + } +} diff --git a/core/src/main/resources/openapi/web3signer-eth2.yaml b/core/src/main/resources/openapi/web3signer-eth2.yaml index ff40705f0..1000393a9 100644 --- a/core/src/main/resources/openapi/web3signer-eth2.yaml +++ b/core/src/main/resources/openapi/web3signer-eth2.yaml @@ -36,6 +36,7 @@ paths: - $ref: '#/components/schemas/AggregateAndProofSigning' - $ref: '#/components/schemas/AttestationSigning' - $ref: '#/components/schemas/BlockSigning' + - $ref: '#/components/schemas/BeaconBlockSigning' - $ref: '#/components/schemas/DepositSigning' - $ref: '#/components/schemas/RandaoRevealSigning' - $ref: '#/components/schemas/VoluntaryExitSigning' @@ -49,6 +50,7 @@ paths: AGGREGATE_AND_PROOF: '#/components/schemas/AggregateAndProofSigning' ATTESTATION: '#/components/schemas/AttestationSigning' BLOCK: '#/components/schemas/BlockSigning' + BLOCK_V2: '#/components/schemas/BeaconBlockSigning' DEPOSIT: '#/components/schemas/DepositSigning' RANDAO_REVEAL: '#/components/schemas/RandaoRevealSigning' VOLUNTARY_EXIT: '#/components/schemas/VoluntaryExitSigning' @@ -505,15 +507,78 @@ components: type: string description: Bytes32 hexadecimal subcommittee_index: - type: string - format: uint64 + type: string + format: uint64 aggregation_bits: type: string description: SSZ hexadecimal signature: type: string description: Bytes96 hexadecimal - + BeaconBlockSigning: + allOf: + - $ref: '#/components/schemas/Signing' + - $ref: '#/components/schemas/BeaconBlockRequest' + BeaconBlockRequest: + type: object + properties: + beacon_block: + anyOf: + - $ref: '#/components/schemas/BlockRequestAltair' + - $ref: '#/components/schemas/BlockRequestPhase0' + required: + - beacon_block + BlockRequestPhase0: + type: object + properties: + version: + type: string + block: + $ref: "#/components/schemas/BeaconBlock" + required: + - version + - block + BlockRequestAltair: + type: object + properties: + version: + type: string + block: + $ref: "#/components/schemas/BeaconBlockAltair" + required: + - version + - block + BeaconBlockAltair: + type: "object" + properties: + slot: + type: "string" + format: "uint64" + proposer_index: + type: "string" + format: "uint64" + parent_root: + type: "string" + state_root: + type: "string" + body: + $ref: "#/components/schemas/BeaconBlockBodyAltair" + BeaconBlockBodyAltair: + allOf: + - $ref: "#/components/schemas/BeaconBlockBody" + - type: object + properties: + sync_aggregate: + $ref: "#/components/schemas/SyncAggregate" + SyncAggregate: + type: object + properties: + sync_committee_bits: + type: string + description: SSZ hexadecimal + sync_committee_signature: + type: string + description: Bytes96 hexadecimal externalDocs: description: 'Web3Signer User Documentation' url: 'https://docs.web3signer.consensys.net/' diff --git a/gradle/license-report-config/allowed-licenses.json b/gradle/license-report-config/allowed-licenses.json index a3748062b..7ff80c99f 100644 --- a/gradle/license-report-config/allowed-licenses.json +++ b/gradle/license-report-config/allowed-licenses.json @@ -83,6 +83,10 @@ "moduleName": "jakarta.xml.bind:jakarta.xml.bind-api", "moduleLicense": "Eclipse Distribution License - v 1.0" }, + { + "moduleName": "jakarta.validation:jakarta.validation-api", + "moduleLicense": "Apache License, Version 2.0" + }, { "moduleName": "javax.xml.bind:jaxb-api", "moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.1" diff --git a/gradle/versions.gradle b/gradle/versions.gradle index f8cf9aa9c..ca53f5d14 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -31,7 +31,7 @@ dependencyManagement { dependency 'info.picocli:picocli:4.5.1' - dependencySet(group: 'io.vertx', version: '3.9.2') { + dependencySet(group: 'io.vertx', version: '3.9.8') { entry 'vertx-codegen' entry 'vertx-core' entry 'vertx-unit' @@ -75,7 +75,7 @@ dependencyManagement { dependency 'org.hyperledger.besu:plugin-api:21.1.3' dependency 'org.hyperledger.besu.internal:metrics-core:21.1.3' - dependencySet(group: 'tech.pegasys.teku.internal', version: '21.8.1') { + dependencySet(group: 'tech.pegasys.teku.internal', version: '21.8.2') { entry 'bls' entry ('core') { exclude 'teku:data' // empty module which is incorrectly referenced in Teku's "core" pom