From a5c3d3104eec88ce29b31f61c415f0fe1def323a Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 27 Nov 2024 15:58:51 +1000 Subject: [PATCH] feat: Commit Boost API - Request Signature -- Routes and Handlers -- Acceptance Test --- CHANGELOG.md | 3 +- ...itBoostGenerateProxyKeyAcceptanceTest.java | 6 +- ... CommitBoostGetPubKeysAcceptanceTest.java} | 33 ++-- ...mmitBoostSigningRequestAcceptanceTest.java | 146 ++++++++++++++++++ .../pegasys/web3signer/core/Eth2Runner.java | 2 + .../CommitBoostRequestSignatureRoute.java | 90 +++++++++++ .../CommitBoostRequestSignatureHandler.java | 84 ++++++++++ .../json/RequestSignatureBody.java | 21 +++ 8 files changed, 369 insertions(+), 16 deletions(-) rename acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/{CommitBoostAcceptanceTest.java => CommitBoostGetPubKeysAcceptanceTest.java} (85%) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostSigningRequestAcceptanceTest.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostRequestSignatureRoute.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostRequestSignatureHandler.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/RequestSignatureBody.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 11b476070..092d6422c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ - Java 21 for build and runtime. [#995](https://github.com/Consensys/web3signer/pull/995) - Electra fork support. [#1020](https://github.com/Consensys/web3signer/pull/1020) and [#1023](https://github.com/Consensys/web3signer/pull/1023) - Teku and Besu libraries updated to 24.10.3 and 24.10.0 respectively. -- Commit Boost API - Get Public Keys [#1031][cb_pr1], Generate Proxy Keys [#1043][cb_pr2]. +- Commit Boost API - Get Public Keys [#1031][cb_pr1], Generate Proxy Keys [#1043][cb_pr2] and Request Signature [#1045][cb_pr3]. [cb_pr1]: https://github.com/Consensys/web3signer/pull/1031 [cb_pr2]: https://github.com/Consensys/web3signer/pull/1043 +[cb_pr3]: https://github.com/Consensys/web3signer/pull/1045 ### Bugs fixed - Override protobuf-java to 3.25.5 which is a transitive dependency from google-cloud-secretmanager. It fixes CVE-2024-7254. diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGenerateProxyKeyAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGenerateProxyKeyAcceptanceTest.java index 23ded2c67..fde2662a7 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGenerateProxyKeyAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGenerateProxyKeyAcceptanceTest.java @@ -14,9 +14,9 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; -import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.KEYSTORE_PASSWORD; -import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.createCommitBoostPasswordFile; -import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.randomBLSKeyPairs; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.KEYSTORE_PASSWORD; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.createCommitBoostPasswordFile; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.randomBLSKeyPairs; import tech.pegasys.teku.bls.BLSKeyPair; import tech.pegasys.teku.networks.Eth2NetworkConfiguration; diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGetPubKeysAcceptanceTest.java similarity index 85% rename from acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostAcceptanceTest.java rename to acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGetPubKeysAcceptanceTest.java index 30bffec81..e7db452a7 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostGetPubKeysAcceptanceTest.java @@ -43,12 +43,13 @@ import org.web3j.crypto.WalletUtils; // See https://commit-boost.github.io/commit-boost-client/api/ for Commit Boost spec -public class CommitBoostAcceptanceTest extends AcceptanceTestBase { +public class CommitBoostGetPubKeysAcceptanceTest extends AcceptanceTestBase { static final String KEYSTORE_PASSWORD = "password"; - private List consensusBlsKeys = randomBLSKeyPairs(2); - private Map> proxyBLSKeysMap = new HashMap<>(); - private Map> proxySECPKeysMap = new HashMap<>(); + private final List consensusBlsKeys = randomBLSKeyPairs(2); + private final Map> proxyBLSKeysMap = new HashMap<>(); + private final Map> proxySECPKeysMap = new HashMap<>(); + @TempDir private Path keystoreDir; @TempDir private Path passwordDir; // commit boost directories @@ -62,11 +63,13 @@ void setup() throws Exception { KeystoreUtil.createKeystore(blsKeyPair, keystoreDir, passwordDir, KEYSTORE_PASSWORD); // create 2 proxy bls - final List proxyBLSKeys = createProxyBLSKeys(blsKeyPair); + final List proxyBLSKeys = + createProxyBLSKeys(blsKeyPair, 2, commitBoostKeystoresPath); proxyBLSKeysMap.put(blsKeyPair.getPublicKey().toHexString(), proxyBLSKeys); // create 2 proxy secp keys - final List proxyECKeyPairs = createProxyECKeys(blsKeyPair); + final List proxyECKeyPairs = + createProxyECKeys(blsKeyPair, 2, commitBoostKeystoresPath); proxySECPKeysMap.put(blsKeyPair.getPublicKey().toHexString(), proxyECKeyPairs); } @@ -147,12 +150,15 @@ static Path createCommitBoostPasswordFile(final Path commitBoostPasswordDir) { } /** - * Generate 2 random proxy EC key pairs and their encrypted keystores + * Generate random proxy EC key pairs and their encrypted keystores for given consensus BLS key * * @param consensusKeyPair consensus BLS key pair whose public key will be used as directory name + * @param count number of proxy key pairs to generate + * @param commitBoostKeystoresPath path to store the generated keystores * @return list of ECKeyPairs */ - private List createProxyECKeys(final BLSKeyPair consensusKeyPair) { + static List createProxyECKeys( + final BLSKeyPair consensusKeyPair, final int count, final Path commitBoostKeystoresPath) { final Path proxySecpKeyStoreDir = commitBoostKeystoresPath .resolve(consensusKeyPair.getPublicKey().toHexString()) @@ -163,7 +169,7 @@ private List createProxyECKeys(final BLSKeyPair consensusKeyPair) { throw new UncheckedIOException(e); } // create 2 random proxy secp keys and their keystores - final List proxyECKeyPairs = randomECKeyPairs(2); + final List proxyECKeyPairs = randomECKeyPairs(count); proxyECKeyPairs.forEach( proxyECKey -> { try { @@ -177,12 +183,15 @@ private List createProxyECKeys(final BLSKeyPair consensusKeyPair) { } /** - * Generate 2 random proxy BLS key pairs and their encrypted keystores + * Generate random proxy BLS key pairs and their encrypted keystores for given BLS consensus key * * @param consensusKeyPair consensus BLS key pair whose public key will be used as directory name + * @param count number of proxy key pairs to generate + * @param commitBoostKeystoresPath path to store the generated keystores * @return list of BLSKeyPairs */ - private List createProxyBLSKeys(final BLSKeyPair consensusKeyPair) { + static List createProxyBLSKeys( + final BLSKeyPair consensusKeyPair, final int count, final Path commitBoostKeystoresPath) { final Path proxyBlsKeyStoreDir = commitBoostKeystoresPath .resolve(consensusKeyPair.getPublicKey().toHexString()) @@ -193,7 +202,7 @@ private List createProxyBLSKeys(final BLSKeyPair consensusKeyPair) { throw new UncheckedIOException(e); } // create 2 proxy bls keys and their keystores - List blsKeyPairs = randomBLSKeyPairs(2); + List blsKeyPairs = randomBLSKeyPairs(count); blsKeyPairs.forEach( blsKeyPair -> KeystoreUtil.createKeystoreFile(blsKeyPair, proxyBlsKeyStoreDir, KEYSTORE_PASSWORD)); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostSigningRequestAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostSigningRequestAcceptanceTest.java new file mode 100644 index 000000000..8c698e55c --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostSigningRequestAcceptanceTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2024 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.commitboost; + +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.KEYSTORE_PASSWORD; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.createCommitBoostPasswordFile; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.createProxyBLSKeys; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.createProxyECKeys; +import static tech.pegasys.web3signer.tests.commitboost.CommitBoostGetPubKeysAcceptanceTest.randomBLSKeyPairs; + +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.networks.Eth2NetworkConfiguration; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.web3signer.KeystoreUtil; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.SigningRootGenerator; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.CommitBoostSignRequestType; +import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder; +import tech.pegasys.web3signer.dsl.utils.DefaultKeystoresParameters; +import tech.pegasys.web3signer.dsl.utils.ValidBLSSignatureMatcher; +import tech.pegasys.web3signer.dsl.utils.ValidK256SignatureMatcher; +import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import tech.pegasys.web3signer.tests.AcceptanceTestBase; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.web3j.crypto.ECKeyPair; + +public class CommitBoostSigningRequestAcceptanceTest extends AcceptanceTestBase { + private static final SigningRootGenerator SIGNING_ROOT_GENERATOR = + new SigningRootGenerator(getMainnetSpec()); + private final List consensusBlsKeys = randomBLSKeyPairs(1); + private final Map> proxyBLSKeysMap = new HashMap<>(); + private final Map> proxySECPKeysMap = new HashMap<>(); + + @TempDir private Path keystoreDir; + @TempDir private Path passwordDir; + // commit boost directories + @TempDir private Path commitBoostKeystoresPath; + @TempDir private Path commitBoostPasswordDir; + + @BeforeEach + void setup() throws Exception { + for (final BLSKeyPair blsKeyPair : consensusBlsKeys) { + // create consensus bls keystore + KeystoreUtil.createKeystore(blsKeyPair, keystoreDir, passwordDir, KEYSTORE_PASSWORD); + + // create 1 proxy bls + final List proxyBLSKeys = + createProxyBLSKeys(blsKeyPair, 1, commitBoostKeystoresPath); + proxyBLSKeysMap.put(blsKeyPair.getPublicKey().toHexString(), proxyBLSKeys); + + // create 1 proxy secp keys + final List proxyECKeyPairs = + createProxyECKeys(blsKeyPair, 1, commitBoostKeystoresPath); + proxySECPKeysMap.put(blsKeyPair.getPublicKey().toHexString(), proxyECKeyPairs); + } + + // commit boost proxy keys password file + final Path commitBoostPasswordFile = createCommitBoostPasswordFile(commitBoostPasswordDir); + + // start web3signer with keystores and commit boost parameters + final KeystoresParameters keystoresParameters = + new DefaultKeystoresParameters(keystoreDir, passwordDir, null); + final Pair commitBoostParameters = + Pair.of(commitBoostKeystoresPath, commitBoostPasswordFile); + + final SignerConfigurationBuilder configBuilder = + new SignerConfigurationBuilder() + .withMode("eth2") + .withNetwork("mainnet") + .withKeystoresParameters(keystoresParameters) + .withCommitBoostParameters(commitBoostParameters); + + startSigner(configBuilder.build()); + } + + @ParameterizedTest + @EnumSource(CommitBoostSignRequestType.class) + void requestCommitBoostSignature(final CommitBoostSignRequestType signRequestType) { + final String consensusPubKey = + consensusBlsKeys.stream().findFirst().orElseThrow().getPublicKey().toHexString(); + final String pubKey = + switch (signRequestType) { + case CONSENSUS -> consensusPubKey; + case PROXY_BLS -> + proxyBLSKeysMap.get(consensusPubKey).stream() + .findFirst() + .orElseThrow() + .getPublicKey() + .toHexString(); + case PROXY_ECDSA -> + EthPublicKeyUtils.toHexStringCompressed( + EthPublicKeyUtils.web3JPublicKeyToECPublicKey( + proxySECPKeysMap.get(consensusPubKey).stream() + .findFirst() + .orElseThrow() + .getPublicKey())); + }; + + // object root is data to sign + final Bytes32 objectRoot = Bytes32.random(new Random(0)); + // signature is calculated on signing root + final Bytes32 signingRoot = SIGNING_ROOT_GENERATOR.computeSigningRoot(objectRoot); + + final Response response = + signer.callCommitBoostRequestForSignature(signRequestType.name(), pubKey, objectRoot); + + response + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body( + signRequestType == CommitBoostSignRequestType.PROXY_ECDSA + ? new ValidK256SignatureMatcher(pubKey, signingRoot) + : new ValidBLSSignatureMatcher(pubKey, signingRoot)); + } + + private static Spec getMainnetSpec() { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + return builder.applyNetworkDefaults(Eth2Network.MAINNET).build().getSpec(); + } +} 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 3137d66f3..eebd89ba7 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -27,6 +27,7 @@ import tech.pegasys.web3signer.core.routes.ReloadRoute; import tech.pegasys.web3signer.core.routes.eth2.CommitBoostGenerateProxyKeyRoute; import tech.pegasys.web3signer.core.routes.eth2.CommitBoostPublicKeysRoute; +import tech.pegasys.web3signer.core.routes.eth2.CommitBoostRequestSignatureRoute; import tech.pegasys.web3signer.core.routes.eth2.Eth2SignExtensionRoute; import tech.pegasys.web3signer.core.routes.eth2.Eth2SignRoute; import tech.pegasys.web3signer.core.routes.eth2.HighWatermarkRoute; @@ -145,6 +146,7 @@ public void populateRouter(final Context context) { if (commitBoostApiParameters.isEnabled()) { new CommitBoostPublicKeysRoute(context).register(); new CommitBoostGenerateProxyKeyRoute(context, commitBoostApiParameters, eth2Spec).register(); + new CommitBoostRequestSignatureRoute(context, eth2Spec).register(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostRequestSignatureRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostRequestSignatureRoute.java new file mode 100644 index 000000000..589ef3bb7 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostRequestSignatureRoute.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024 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.routes.eth2; + +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; + +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.web3signer.core.Context; +import tech.pegasys.web3signer.core.routes.Web3SignerRoute; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.CommitBoostRequestSignatureHandler; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; + +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonObject; + +public class CommitBoostRequestSignatureRoute implements Web3SignerRoute { + private static final String PATH = "/signer/v1/request_signature"; + private final Context context; + private final Spec eth2Spec; + private final ArtifactSignerProvider artifactSignerProvider; + + public CommitBoostRequestSignatureRoute(final Context context, final Spec eth2Spec) { + this.context = context; + this.eth2Spec = eth2Spec; + + // there should be only one DefaultArtifactSignerProvider in eth2 mode + artifactSignerProvider = + context.getArtifactSignerProviders().stream() + .filter(p -> p instanceof DefaultArtifactSignerProvider) + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "No DefaultArtifactSignerProvider found in Context for eth2 mode")); + } + + @Override + public void register() { + context + .getRouter() + .route(HttpMethod.POST, PATH) + .blockingHandler( + new CommitBoostRequestSignatureHandler(artifactSignerProvider, eth2Spec), false) + .failureHandler(context.getErrorHandler()) + .failureHandler( + ctx -> { + final int statusCode = ctx.statusCode(); + if (statusCode == HTTP_BAD_REQUEST) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Bad Request") + .encode()); + } else if (statusCode == HTTP_NOT_FOUND) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Unknown pubkey") + .encode()); + } else if (statusCode == HTTP_INTERNAL_ERROR) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Internal Error") + .encode()); + } else { + ctx.next(); // go to global failure handler + } + }); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostRequestSignatureHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostRequestSignatureHandler.java new file mode 100644 index 000000000..3b4b253cd --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostRequestSignatureHandler.java @@ -0,0 +1,84 @@ +/* + * Copyright 2024 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.commitboost; + +import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE; +import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; +import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.JSON_UTF_8; +import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier; + +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.RequestSignatureBody; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; + +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import org.apache.tuweni.bytes.Bytes32; + +public class CommitBoostRequestSignatureHandler implements Handler { + private static final ObjectMapper JSON_MAPPER = SigningObjectMapperFactory.createObjectMapper(); + + private final CommitBoostSignerProvider commitBoostSigner; + private final SigningRootGenerator signingRootGenerator; + + public CommitBoostRequestSignatureHandler( + final ArtifactSignerProvider artifactSignerProvider, final Spec eth2Spec) { + commitBoostSigner = new CommitBoostSignerProvider(artifactSignerProvider); + signingRootGenerator = new SigningRootGenerator(eth2Spec); + } + + @Override + public void handle(final RoutingContext context) { + final String body = context.body().asString(); + + // read and validate incoming json body + final RequestSignatureBody requestSignatureBody; + try { + requestSignatureBody = JSON_MAPPER.readValue(body, RequestSignatureBody.class); + } catch (final JsonProcessingException | IllegalArgumentException e) { + context.fail(HTTP_BAD_REQUEST); + return; + } + try { + // Check for pubkey based on signing type, if not exist, fail with 404 + final String identifier = normaliseIdentifier(requestSignatureBody.publicKey()); + if (!commitBoostSigner.isSignerAvailable(identifier, requestSignatureBody.type())) { + context.fail(HTTP_NOT_FOUND); + return; + } + + // Calculate Signing root and sign the request + final Bytes32 signingRoot = + signingRootGenerator.computeSigningRoot(requestSignatureBody.objectRoot()); + final Optional optionalSig = + commitBoostSigner.sign(identifier, requestSignatureBody.type(), signingRoot); + if (optionalSig.isEmpty()) { + context.fail(HTTP_NOT_FOUND); + return; + } + + // Encode and send response + final String jsonEncoded = JSON_MAPPER.writeValueAsString(optionalSig.get()); + context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); + } catch (final Exception e) { + context.fail(HTTP_INTERNAL_ERROR, e); + } + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/RequestSignatureBody.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/RequestSignatureBody.java new file mode 100644 index 000000000..372873cad --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/RequestSignatureBody.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024 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.commitboost.json; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes32; + +public record RequestSignatureBody( + @JsonProperty(value = "type", required = true) CommitBoostSignRequestType type, + @JsonProperty(value = "pubkey", required = true) String publicKey, + @JsonProperty(value = "object_root", required = true) Bytes32 objectRoot) {}