From 764517e1b2c99aee8e549ec639a8176b68310061 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 20 Oct 2024 12:50:13 +1000 Subject: [PATCH 01/57] feat: Commit boost API - Get Public Keys -- Implement route and handlers for /signer_v1_get_pubkeys --- .../pegasys/web3signer/core/Eth2Runner.java | 2 + .../eth2/CommitBoostPublicKeysRoute.java | 53 ++++++++++++ .../CommitBoostPublicKeysHandler.java | 81 +++++++++++++++++++ .../commitboost/json/PublicKeyMappings.java | 29 +++++++ .../commitboost/json/PublicKeysResponse.java | 20 +++++ .../web3signer/signing/ArtifactSigner.java | 2 + .../signing/ArtifactSignerProvider.java | 39 +++++++++ .../web3signer/signing/BlsArtifactSigner.java | 5 ++ .../signing/EthSecpArtifactSigner.java | 5 ++ .../config/DefaultArtifactSignerProvider.java | 16 ++++ .../SecpArtifactSignerProviderAdapter.java | 7 ++ 11 files changed, 259 insertions(+) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeysResponse.java 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 e0807a591..22689ce92 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -25,6 +25,7 @@ import tech.pegasys.web3signer.core.config.BaseConfig; import tech.pegasys.web3signer.core.routes.PublicKeysListRoute; import tech.pegasys.web3signer.core.routes.ReloadRoute; +import tech.pegasys.web3signer.core.routes.eth2.CommitBoostPublicKeysRoute; import tech.pegasys.web3signer.core.routes.eth2.Eth2SignExtensionRoute; import tech.pegasys.web3signer.core.routes.eth2.Eth2SignRoute; import tech.pegasys.web3signer.core.routes.eth2.HighWatermarkRoute; @@ -137,6 +138,7 @@ public void populateRouter(final Context context) { if (isKeyManagerApiEnabled) { new KeyManagerApiRoute(context, baseConfig, slashingProtectionContext).register(); } + new CommitBoostPublicKeysRoute(context).register(); } @Override diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java new file mode 100644 index 000000000..fac7062de --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java @@ -0,0 +1,53 @@ +/* + * 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 tech.pegasys.web3signer.core.Context; +import tech.pegasys.web3signer.core.routes.Web3SignerRoute; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.CommitBoostPublicKeysHandler; + +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.impl.BlockingHandlerDecorator; + +public class CommitBoostPublicKeysRoute implements Web3SignerRoute { + private static final String PATH = "/signer/v1/get_pubkeys"; + private final Context context; + + public CommitBoostPublicKeysRoute(final Context context) { + this.context = context; + } + + @Override + public void register() { + context + .getRouter() + .route(HttpMethod.GET, PATH) + .produces(JSON_HEADER) + .handler( + new BlockingHandlerDecorator( + new CommitBoostPublicKeysHandler(context.getArtifactSignerProviders()), false)) + .failureHandler(context.getErrorHandler()) + .failureHandler( + ctx -> { + final int statusCode = ctx.statusCode(); + if (statusCode == 500) { + ctx.response() + .setStatusCode(statusCode) + .end(new JsonObject().put("error", "Internal Server 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/CommitBoostPublicKeysHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java new file mode 100644 index 000000000..12a268195 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java @@ -0,0 +1,81 @@ +/* + * 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 tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.JSON_UTF_8; + +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.PublicKeyMappings; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.PublicKeysResponse; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.KeyType; +import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +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.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CommitBoostPublicKeysHandler implements Handler { + private static final Logger LOG = LogManager.getLogger(); + private final List artifactSignerProviders; + private final ObjectMapper objectMapper = SigningObjectMapperFactory.createObjectMapper(); + + public CommitBoostPublicKeysHandler(final List artifactSignerProviders) { + this.artifactSignerProviders = artifactSignerProviders; + } + + @Override + public void handle(final RoutingContext context) { + // obtain DefaultArtifactSignerProvider as that is the only one we are dealing in eth2 mode. + final ArtifactSignerProvider artifactSignerProvider = + artifactSignerProviders.stream() + .filter(provider -> provider instanceof DefaultArtifactSignerProvider) + .findFirst() + .orElseThrow(); + + final PublicKeysResponse publicKeysResponse = toPublicKeysResponse(artifactSignerProvider); + try { + final String jsonEncoded = objectMapper.writeValueAsString(publicKeysResponse); + context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); + } catch (final JsonProcessingException e) { + // this is not meant to happen + LOG.error("Failed to encode public keys response", e); + context.fail(500); + } + } + + private PublicKeysResponse toPublicKeysResponse(final ArtifactSignerProvider provider) { + return new PublicKeysResponse( + provider.availableIdentifiers().stream() + .map(identifier -> toPublicKeyMappings(provider, identifier)) + .collect(Collectors.toList())); + } + + private static PublicKeyMappings toPublicKeyMappings( + final ArtifactSignerProvider provider, final String identifier) { + final Map> proxyIdentifiers = provider.getProxyIdentifiers(identifier); + final List proxyBlsPublicKeys = + proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> List.of()); + final List proxyEcdsaPublicKeys = + proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> List.of()); + return new PublicKeyMappings(identifier, proxyBlsPublicKeys, proxyEcdsaPublicKeys); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java new file mode 100644 index 000000000..08ca0690b --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java @@ -0,0 +1,29 @@ +/* + * 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 java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents the public key mappings for get_pubkeys API + * + * @param consensus BLS Public Key in hex string format + * @param proxyBlsPublicKeys List of Proxy BLS Public Key in hex string format + * @param proxyEcdsaPublicKeys List of Proxy ECDSA (SECP256K1) Public Key in hex string format + */ +public record PublicKeyMappings( + @JsonProperty(value = "consensus") String consensus, + @JsonProperty(value = "proxy_bls") List proxyBlsPublicKeys, + @JsonProperty(value = "proxy_ecdsa") List proxyEcdsaPublicKeys) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeysResponse.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeysResponse.java new file mode 100644 index 000000000..3eb9c4fc4 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeysResponse.java @@ -0,0 +1,20 @@ +/* + * 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 java.util.List; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record PublicKeysResponse( + @JsonProperty(value = "keys") List publicKeys) {} diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSigner.java index c17355a85..01acf07b0 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSigner.java @@ -19,4 +19,6 @@ public interface ArtifactSigner { String getIdentifier(); ArtifactSignature sign(final Bytes message); + + KeyType getKeyType(); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java index 565f6068b..985100271 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java @@ -13,22 +13,61 @@ package tech.pegasys.web3signer.signing; import java.io.Closeable; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Future; public interface ArtifactSignerProvider extends Closeable { + /** + * Load the signers from the underlying providers. + * + * @return a future that completes when the signers are loaded + */ Future load(); + /** + * Get the signer for the given identifier. + * + * @param identifier the identifier of the signer + * @return the signer or empty if no signer is found + */ Optional getSigner(final String identifier); + /** + * Get the available identifiers for the loaded signers. + * + * @return the available identifiers + */ Set availableIdentifiers(); + /** + * Get the proxy identifiers for the given identifier. Used for commit boost API. + * + * @param identifier the identifier of the signer + * @return Map of Key Type (BLS, SECP256K1) and corresponding proxy identifiers + */ + Map> getProxyIdentifiers(final String identifier); + + /** + * Add a new signer to the signer provider. + * + * @param signer the signer to add + * @return a future that completes when the signer is added + */ Future addSigner(final ArtifactSigner signer); + /** + * Remove a signer from the signer provider. + * + * @param identifier signer to remove + * @return a future that completes when the signer is removed + */ Future removeSigner(final String identifier); + /** Close the executor service and release any resources. */ @Override void close(); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSigner.java index 7dbc2566f..c7a04b131 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSigner.java @@ -51,6 +51,11 @@ public BlsArtifactSignature sign(final Bytes data) { return new BlsArtifactSignature(BLS.sign(keyPair.getSecretKey(), data)); } + @Override + public KeyType getKeyType() { + return KeyType.BLS; + } + public Optional getPath() { return path; } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java index 7335b885f..7e572c5f5 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java @@ -38,6 +38,11 @@ public SecpArtifactSignature sign(final Bytes message) { return new SecpArtifactSignature(signer.sign(message.toArray())); } + @Override + public KeyType getKeyType() { + return KeyType.SECP256K1; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 696f1ab32..fada692b8 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -14,9 +14,11 @@ import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.KeyType; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -35,6 +37,7 @@ public class DefaultArtifactSignerProvider implements ArtifactSignerProvider { private static final Logger LOG = LogManager.getLogger(); private final Supplier> artifactSignerCollectionSupplier; private final Map signers = new HashMap<>(); + private final Map> proxySigners = new HashMap<>(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); public DefaultArtifactSignerProvider( @@ -60,6 +63,8 @@ public Future load() { })) .forEach(signers::putIfAbsent); + // TODO: for each loaded signer, load proxy signers (if any) + LOG.info("Total signers (keys) currently loaded in memory: {}", signers.size()); return null; }); @@ -80,6 +85,17 @@ public Set availableIdentifiers() { return Set.copyOf(signers.keySet()); } + @Override + public Map> getProxyIdentifiers(final String identifier) { + final List artifactSigners = + proxySigners.computeIfAbsent(identifier, k -> List.of()); + return artifactSigners.stream() + .collect( + Collectors.groupingBy( + ArtifactSigner::getKeyType, + Collectors.mapping(ArtifactSigner::getIdentifier, Collectors.toList()))); + } + @Override public Future addSigner(final ArtifactSigner signer) { return executorService.submit( diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java index 9155694ba..36578bcdf 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java @@ -17,8 +17,10 @@ import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.KeyType; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -66,6 +68,11 @@ public Set availableIdentifiers() { return Set.copyOf(signers.keySet()); } + @Override + public Map> getProxyIdentifiers(final String identifier) { + throw new NotImplementedException(); + } + @Override public Future addSigner(final ArtifactSigner signer) { throw new NotImplementedException(); From 82ce60b85dbc83d9d5a14ff77e162a1dba97d7f5 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 20 Oct 2024 18:40:32 +1000 Subject: [PATCH 02/57] Cli options for commit boost. Load proxy signers --- .../config/PicoCommitBoostApiParameters.java | 88 +++++++++++++++++++ .../subcommands/Eth2SubCommand.java | 6 +- .../commandline/CommandlineParserTest.java | 44 +++++++++- .../pegasys/web3signer/core/Eth1Runner.java | 4 +- .../pegasys/web3signer/core/Eth2Runner.java | 12 ++- .../config/DefaultArtifactSignerProvider.java | 53 ++++++++++- .../DefaultArtifactSignerProviderTest.java | 10 ++- 7 files changed, 206 insertions(+), 11 deletions(-) create mode 100644 commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java new file mode 100644 index 000000000..cf2c4293d --- /dev/null +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 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.commandline.config; + +import static tech.pegasys.web3signer.commandline.DefaultCommandValues.PATH_FORMAT_HELP; + +import tech.pegasys.web3signer.signing.config.KeystoresParameters; + +import java.nio.file.Path; + +import picocli.CommandLine; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Spec; + +public class PicoCommitBoostApiParameters implements KeystoresParameters { + @Spec private CommandSpec commandSpec; // injected by picocli + + @CommandLine.Option( + names = {"--commit-boost-api-enabled"}, + paramLabel = "", + description = "Enable the commit boost API (default: ${DEFAULT-VALUE}).", + arity = "1") + private boolean isCommitBoostApiEnabled = false; + + @Option( + names = {"--proxy-keystores-path"}, + description = + "The path to a writeable directory to store v3 and v4 proxy keystores for commit boost API.", + paramLabel = PATH_FORMAT_HELP) + private Path keystoresPath; + + @Option( + names = {"--proxy-keystores-password-file"}, + description = + "The path to the password file used to encrypt/decrypt proxy keystores for commit boost API.", + paramLabel = PATH_FORMAT_HELP) + private Path keystoresPasswordFile; + + @Override + public Path getKeystoresPath() { + return keystoresPath; + } + + @Override + public Path getKeystoresPasswordsPath() { + return null; + } + + @Override + public Path getKeystoresPasswordFile() { + return keystoresPasswordFile; + } + + @Override + public boolean isEnabled() { + return isCommitBoostApiEnabled; + } + + public void validateParameters() throws ParameterException { + if (!isCommitBoostApiEnabled) { + return; + } + + if (keystoresPath == null) { + throw new ParameterException( + commandSpec.commandLine(), + "Commit boost API is enabled, but --proxy-keystores-path not set"); + } + + if (keystoresPasswordFile == null) { + throw new ParameterException( + commandSpec.commandLine(), + "Commit boost API is enabled, but --proxy-keystores-password-file not set"); + } + } +} 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 d80a4efdd..92bf6ef19 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 @@ -29,6 +29,7 @@ import tech.pegasys.web3signer.commandline.PicoCliEth2AzureKeyVaultParameters; import tech.pegasys.web3signer.commandline.PicoCliGcpSecretManagerParameters; import tech.pegasys.web3signer.commandline.PicoCliSlashingProtectionParameters; +import tech.pegasys.web3signer.commandline.config.PicoCommitBoostApiParameters; import tech.pegasys.web3signer.commandline.config.PicoKeystoresParameters; import tech.pegasys.web3signer.common.config.AwsAuthenticationMode; import tech.pegasys.web3signer.core.Eth2Runner; @@ -164,6 +165,7 @@ private static class NetworkCliCompletionCandidates extends ArrayList { @Mixin private PicoKeystoresParameters keystoreParameters; @Mixin private PicoCliAwsSecretsManagerParameters awsSecretsManagerParameters; @Mixin private PicoCliGcpSecretManagerParameters gcpSecretManagerParameters; + @Mixin private PicoCommitBoostApiParameters commitBoostApiParameters; private tech.pegasys.teku.spec.Spec eth2Spec; public Eth2SubCommand() { @@ -183,7 +185,8 @@ public Runner createRunner() { gcpSecretManagerParameters, eth2Spec, isKeyManagerApiEnabled, - signingExtEnabled); + signingExtEnabled, + commitBoostApiParameters); } private void logNetworkSpecInformation() { @@ -261,6 +264,7 @@ protected void validateArgs() { validateKeystoreParameters(keystoreParameters); validateAwsSecretsManageParameters(); validateGcpSecretManagerParameters(); + commitBoostApiParameters.validateParameters(); } private void validateGcpSecretManagerParameters() { diff --git a/commandline/src/test/java/tech/pegasys/web3signer/commandline/CommandlineParserTest.java b/commandline/src/test/java/tech/pegasys/web3signer/commandline/CommandlineParserTest.java index 7d54692f7..23bb35904 100644 --- a/commandline/src/test/java/tech/pegasys/web3signer/commandline/CommandlineParserTest.java +++ b/commandline/src/test/java/tech/pegasys/web3signer/commandline/CommandlineParserTest.java @@ -37,6 +37,7 @@ import java.net.InetAddress; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import io.vertx.core.Vertx; @@ -612,6 +613,47 @@ void vertxWorkerPoolSizeParsesSuccessfully() { assertThat(mockEth2SubCommand.getConfig().getVertxWorkerPoolSize()).isEqualTo(40); } + @Test + void commitBoostApiEnabledWithoutKeystorePathFailsToParse() { + String cmdline = validBaseCommandOptions(); + cmdline += "eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true"; + + parser.registerSubCommands(new MockEth2SubCommand()); + final int result = parser.parseCommandLine(cmdline.split(" ")); + + assertThat(result).isNotZero(); + assertThat(commandError.toString()) + .contains( + "Error parsing parameters: Commit boost API is enabled, but --proxy-keystores-path not set"); + } + + @Test + void commitBoostApiEnabledWithoutKeystorePasswordFileFailsToParse() { + String cmdline = validBaseCommandOptions(); + cmdline += + "eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true --proxy-keystores-path=./keystore"; + + parser.registerSubCommands(new MockEth2SubCommand()); + final int result = parser.parseCommandLine(cmdline.split(" ")); + + assertThat(result).isNotZero(); + assertThat(commandError.toString()) + .contains( + "Error parsing parameters: Commit boost API is enabled, but --proxy-keystores-password-file not set"); + } + + @Test + void commitBoostApiEnabledWithKeystorePathAndKeystorePasswordFileParsesSuccessfully() { + String cmdline = validBaseCommandOptions(); + cmdline += + "eth2 --slashing-protection-enabled=false --commit-boost-api-enabled=true --proxy-keystores-path=./keystore --proxy-keystores-password-file=./password"; + + parser.registerSubCommands(new MockEth2SubCommand()); + final int result = parser.parseCommandLine(cmdline.split(" ")); + + assertThat(result).isZero(); + } + private void missingOptionalParameterIsValidAndMeetsDefault( final String paramToRemove, final Supplier actualValueGetter, final T expectedValue) { @@ -646,7 +688,7 @@ public void run() {} @Override protected List createArtifactSignerProvider( final Vertx vertx, final MetricsSystem metricsSystem) { - return List.of(new DefaultArtifactSignerProvider(Collections::emptyList)); + return List.of(new DefaultArtifactSignerProvider(Collections::emptyList, Optional.empty())); } @Override diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java index 0fa92854e..4a470d229 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; @@ -114,7 +115,8 @@ protected List createArtifactSignerProvider( awsKmsSignerFactory) .getValues()); return signers; - }); + }, + Optional.empty()); // uses eth1 address as identifier final ArtifactSignerProvider secpArtifactSignerProvider = 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 22689ce92..dd1a3abdf 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -89,6 +89,7 @@ public class Eth2Runner extends Runner { private final Spec eth2Spec; private final boolean isKeyManagerApiEnabled; private final boolean signingExtEnabled; + private final KeystoresParameters commitBoostApiParameters; public Eth2Runner( final BaseConfig baseConfig, @@ -99,7 +100,8 @@ public Eth2Runner( final GcpSecretManagerParameters gcpSecretManagerParameters, final Spec eth2Spec, final boolean isKeyManagerApiEnabled, - final boolean signingExtEnabled) { + final boolean signingExtEnabled, + final KeystoresParameters commitBoostApiParameters) { super(baseConfig); this.slashingProtectionContext = createSlashingProtection(slashingProtectionParameters); this.azureKeyVaultParameters = azureKeyVaultParameters; @@ -111,6 +113,7 @@ public Eth2Runner( this.awsVaultParameters = awsVaultParameters; this.gcpSecretManagerParameters = gcpSecretManagerParameters; this.signingExtEnabled = signingExtEnabled; + this.commitBoostApiParameters = commitBoostApiParameters; } private Optional createSlashingProtection( @@ -138,7 +141,9 @@ public void populateRouter(final Context context) { if (isKeyManagerApiEnabled) { new KeyManagerApiRoute(context, baseConfig, slashingProtectionContext).register(); } - new CommitBoostPublicKeysRoute(context).register(); + if (commitBoostApiParameters.isEnabled()) { + new CommitBoostPublicKeysRoute(context).register(); + } } @Override @@ -168,7 +173,8 @@ protected List createArtifactSignerProvider( return signers; } - })); + }, + Optional.of(commitBoostApiParameters))); } private MappedResults loadSignersFromKeyConfigFiles( diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index fada692b8..5d0e6f956 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -12,10 +12,15 @@ */ package tech.pegasys.web3signer.signing.config; +import tech.pegasys.web3signer.keystorage.common.MappedResults; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; import tech.pegasys.web3signer.signing.KeyType; +import tech.pegasys.web3signer.signing.bulkloading.BlsKeystoreBulkLoader; +import tech.pegasys.web3signer.signing.bulkloading.SecpV3KeystoresBulkLoader; +import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -39,10 +44,13 @@ public class DefaultArtifactSignerProvider implements ArtifactSignerProvider { private final Map signers = new HashMap<>(); private final Map> proxySigners = new HashMap<>(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final Optional commitBoostKeystoresParameters; public DefaultArtifactSignerProvider( - final Supplier> artifactSignerCollectionSupplier) { + final Supplier> artifactSignerCollectionSupplier, + final Optional commitBoostKeystoresParameters) { this.artifactSignerCollectionSupplier = artifactSignerCollectionSupplier; + this.commitBoostKeystoresParameters = commitBoostKeystoresParameters; } @Override @@ -63,7 +71,48 @@ public Future load() { })) .forEach(signers::putIfAbsent); - // TODO: for each loaded signer, load proxy signers (if any) + // for each loaded signer, load commit boost proxy signers (if any) + commitBoostKeystoresParameters.ifPresent( + keystoreParameter -> { + if (!keystoreParameter.isEnabled()) { + return; + } + signers.forEach( + (identifier, __) -> { + final Path identifierPath = + keystoreParameter.getKeystoresPath().resolve(identifier); + if (identifierPath.toFile().canRead() + && identifierPath.toFile().isDirectory()) { + final Path v4Dir = identifierPath.resolve("v4"); + + if (v4Dir.toFile().canRead() && v4Dir.toFile().isDirectory()) { + // load v4 proxy signers + final BlsKeystoreBulkLoader v4Loader = new BlsKeystoreBulkLoader(); + final MappedResults blsSignersResult = + v4Loader.loadKeystoresUsingPasswordFile( + v4Dir, keystoreParameter.getKeystoresPasswordFile()); + final Collection blsSigners = + blsSignersResult.getValues(); + proxySigners + .computeIfAbsent(identifier, k -> new ArrayList<>()) + .addAll(blsSigners); + } + + final Path v3Dir = identifierPath.resolve("v3"); + if (v3Dir.toFile().canRead() && v3Dir.toFile().isDirectory()) { + // load v3 proxy signers + final MappedResults secpSignersResults = + SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( + v3Dir, keystoreParameter.getKeystoresPasswordFile()); + final Collection secpSigners = + secpSignersResults.getValues(); + proxySigners + .computeIfAbsent(identifier, k -> new ArrayList<>()) + .addAll(secpSigners); + } + } + }); + }); LOG.info("Total signers (keys) currently loaded in memory: {}", signers.size()); return null; diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 911282608..ef1e939ff 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -47,7 +47,7 @@ void signerReturnedForMatchingIdentifier() { final ArtifactSigner mockSigner = mock(ArtifactSigner.class); when(mockSigner.getIdentifier()).thenReturn(PUBLIC_KEY1); - signerProvider = new DefaultArtifactSignerProvider(() -> List.of(mockSigner)); + signerProvider = new DefaultArtifactSignerProvider(() -> List.of(mockSigner), Optional.empty()); assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); final Optional signer = signerProvider.getSigner(PUBLIC_KEY1); @@ -62,7 +62,9 @@ void signerProviderOnlyHasSingleEntryIfPassedInListHasMultipleMatchingSigners() final ArtifactSigner mockSigner2 = mock(ArtifactSigner.class); when(mockSigner2.getIdentifier()).thenReturn(PUBLIC_KEY1); - signerProvider = new DefaultArtifactSignerProvider(() -> List.of(mockSigner1, mockSigner2)); + signerProvider = + new DefaultArtifactSignerProvider( + () -> List.of(mockSigner1, mockSigner2), Optional.empty()); assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); assertThat(signerProvider.availableIdentifiers()).hasSize(1); @@ -76,7 +78,9 @@ void signerProviderCanMapInTwoSigners() { final ArtifactSigner mockSigner2 = mock(ArtifactSigner.class); when(mockSigner2.getIdentifier()).thenReturn(PUBLIC_KEY2); - signerProvider = new DefaultArtifactSignerProvider(() -> List.of(mockSigner1, mockSigner2)); + signerProvider = + new DefaultArtifactSignerProvider( + () -> List.of(mockSigner1, mockSigner2), Optional.empty()); assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); assertThat(signerProvider.availableIdentifiers()).hasSize(2); assertThat(signerProvider.availableIdentifiers()).containsOnly(PUBLIC_KEY1, PUBLIC_KEY2); From cd20c6cf4e2f2cf71cee3b48aeb9c991ca6b9a3e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 20 Oct 2024 20:20:31 +1000 Subject: [PATCH 03/57] Unit test for DefaultArtifactSignerProvider loading proxy keys --- .../DefaultArtifactSignerProviderTest.java | 161 +++++++++++++++++- 1 file changed, 158 insertions(+), 3 deletions(-) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index ef1e939ff..770ee0e7c 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -17,24 +17,45 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.web3signer.KeystoreUtil; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; - +import tech.pegasys.web3signer.signing.KeyType; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Keys; +import org.web3j.crypto.WalletUtils; +import org.web3j.crypto.exception.CipherException; class DefaultArtifactSignerProviderTest { private static final String PUBLIC_KEY1 = - "989d34725a2bfc3f15105f3f5fc8741f436c25ee1ee4f948e425d6bcb8c56bce6e06c269635b7e985a7ffa639e2409bf"; + "0x989d34725a2bfc3f15105f3f5fc8741f436c25ee1ee4f948e425d6bcb8c56bce6e06c269635b7e985a7ffa639e2409bf"; private static final String PUBLIC_KEY2 = - "a99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"; private ArtifactSignerProvider signerProvider; + @TempDir private Path commitBoostKeystoresPath; + + @TempDir private Path commitBoostPasswordDir; + @AfterEach void cleanup() { if (signerProvider != null) { @@ -85,4 +106,138 @@ void signerProviderCanMapInTwoSigners() { assertThat(signerProvider.availableIdentifiers()).hasSize(2); assertThat(signerProvider.availableIdentifiers()).containsOnly(PUBLIC_KEY1, PUBLIC_KEY2); } + + @Test + void proxySignersAreLoadedCorrectly() throws IOException { + final SecureRandom secureRandom = new SecureRandom(); + + // create random proxy signers + final KeystoresParameters commitBoostParameters = + new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); + + // create random BLS key pairs as proxy keys for public key1 and public key2 + final List key1ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY1); + final List key2ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY2); + + // create random secp key pairs as proxy keys for public key1 and public key2 + final List key1SecpKeyPairs = randomSecpV3Keystores(secureRandom, PUBLIC_KEY1); + final List key2SecpKeyPairs = randomSecpV3Keystores(secureRandom, PUBLIC_KEY2); + + // set up mock signers + final ArtifactSigner mockSigner1 = mock(ArtifactSigner.class); + when(mockSigner1.getIdentifier()).thenReturn(PUBLIC_KEY1); + final ArtifactSigner mockSigner2 = mock(ArtifactSigner.class); + when(mockSigner2.getIdentifier()).thenReturn(PUBLIC_KEY2); + + signerProvider = + new DefaultArtifactSignerProvider( + () -> List.of(mockSigner1, mockSigner2), Optional.of(commitBoostParameters)); + + // methods under test + assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); + + // assert that the proxy keys are loaded correctly + final Map> key1ProxyPublicKeys = + signerProvider.getProxyIdentifiers(PUBLIC_KEY1); + + assertThat(key1ProxyPublicKeys.get(KeyType.BLS)) + .containsExactlyInAnyOrder(getPublicKeysArray(key1ProxyKeyPairs)); + assertThat(key1ProxyPublicKeys.get(KeyType.SECP256K1)) + .containsExactlyInAnyOrder(getSecpPublicKeysArray(key1SecpKeyPairs)); + + final Map> key2ProxyPublicKeys = + signerProvider.getProxyIdentifiers(PUBLIC_KEY2); + + assertThat(key2ProxyPublicKeys.get(KeyType.BLS)) + .containsExactlyInAnyOrder(getPublicKeysArray(key2ProxyKeyPairs)); + assertThat(key2ProxyPublicKeys.get(KeyType.SECP256K1)) + .containsExactlyInAnyOrder(getSecpPublicKeysArray(key2SecpKeyPairs)); + } + + private List randomBLSV4Keystores(SecureRandom secureRandom, String identifier) + throws IOException { + final Path v4Dir = + Files.createDirectories(commitBoostKeystoresPath.resolve(identifier).resolve("v4")); + + return IntStream.range(0, 4) + .mapToObj( + i -> { + final BLSKeyPair blsKeyPair = BLSKeyPair.random(secureRandom); + KeystoreUtil.createKeystoreFile(blsKeyPair, v4Dir, "password"); + return blsKeyPair; + }) + .toList(); + } + + private List randomSecpV3Keystores( + final SecureRandom secureRandom, final String identifier) throws IOException { + final Path v3Dir = + Files.createDirectories(commitBoostKeystoresPath.resolve(identifier).resolve("v3")); + return IntStream.range(0, 4) + .mapToObj( + i -> { + try { + final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); + WalletUtils.generateWalletFile("password", ecKeyPair, v3Dir.toFile(), false); + return ecKeyPair; + } catch (GeneralSecurityException | CipherException | IOException e) { + throw new RuntimeException(e); + } + }) + .toList(); + } + + private static String[] getPublicKeysArray(final List blsKeyPairs) { + return blsKeyPairs.stream() + .map(keyPair -> keyPair.getPublicKey().toString()) + .toList() + .toArray(String[]::new); + } + + private static String[] getSecpPublicKeysArray(final List ecKeyPairs) { + return ecKeyPairs.stream() + .map( + keyPair -> + EthPublicKeyUtils.toHexString( + EthPublicKeyUtils.createPublicKey(keyPair.getPublicKey()))) + .toList() + .toArray(String[]::new); + } + + private static class TestCommitBoostParameters implements KeystoresParameters { + private final Path keystorePath; + private final Path passwordFile; + + public TestCommitBoostParameters(final Path keystorePath, final Path passwordDir) { + this.keystorePath = keystorePath; + // create password file in passwordDir + this.passwordFile = passwordDir.resolve("password.txt"); + // write text to password file + try { + Files.writeString(passwordFile, "password"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public Path getKeystoresPath() { + return keystorePath; + } + + @Override + public Path getKeystoresPasswordsPath() { + return null; + } + + @Override + public Path getKeystoresPasswordFile() { + return passwordFile; + } + + @Override + public boolean isEnabled() { + return true; + } + } } From 94d9be43f775d175d1dd00ba38522932b9d98c94 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 21 Oct 2024 10:27:55 +1000 Subject: [PATCH 04/57] empty proxy signers unit test --- .../DefaultArtifactSignerProviderTest.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 770ee0e7c..2c8043f20 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -154,6 +154,32 @@ void proxySignersAreLoadedCorrectly() throws IOException { .containsExactlyInAnyOrder(getSecpPublicKeysArray(key2SecpKeyPairs)); } + @Test + void emptyProxySignersAreLoadedSuccessfully() { + // enable commit boose without existing proxy keys + final KeystoresParameters commitBoostParameters = + new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); + + // set up mock signers + final ArtifactSigner mockSigner1 = mock(ArtifactSigner.class); + when(mockSigner1.getIdentifier()).thenReturn(PUBLIC_KEY1); + final ArtifactSigner mockSigner2 = mock(ArtifactSigner.class); + when(mockSigner2.getIdentifier()).thenReturn(PUBLIC_KEY2); + + signerProvider = + new DefaultArtifactSignerProvider( + () -> List.of(mockSigner1, mockSigner2), Optional.of(commitBoostParameters)); + + // methods under test + assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); + + for (String identifier : List.of(PUBLIC_KEY1, PUBLIC_KEY2)) { + final Map> keyProxyPublicKeys = + signerProvider.getProxyIdentifiers(identifier); + assertThat(keyProxyPublicKeys).isEmpty(); + } + } + private List randomBLSV4Keystores(SecureRandom secureRandom, String identifier) throws IOException { final Path v4Dir = From 3433f7541dbe21863376be964067139d33246c80 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 21 Oct 2024 10:43:19 +1000 Subject: [PATCH 05/57] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 109c08b9f..5fab48afe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Features Added - 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) +- Commit boost API - Get Public Keys. [#1031](https://github.com/Consensys/web3signer/pull/1031) ### Bugs fixed - Override protobuf-java to 3.25.5 which is a transitive dependency from google-cloud-secretmanager. It fixes CVE-2024-7254. From 9277a3ef85934f624b7f01b1bf7cab7f80e4dcb1 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 21 Oct 2024 15:59:16 +1000 Subject: [PATCH 06/57] feat: Commit boost API - Generate Proxy Key -- Added routes and handler --- .../CommitBoostGenerateProxyKeyRoute.java | 87 +++++++++++++++++++ .../eth2/CommitBoostPublicKeysRoute.java | 6 +- .../CommitBoostGenerateProxyKeyHandler.java | 85 ++++++++++++++++++ .../json/GenerateProxyKeyBody.java | 19 ++++ .../json/GenerateProxyKeyResponse.java | 19 ++++ .../commitboost/json/ProxyKeyMessage.java | 19 ++++ .../json/ProxyKeySignatureScheme.java | 18 ++++ 7 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeySignatureScheme.java diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java new file mode 100644 index 000000000..23dd04ace --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java @@ -0,0 +1,87 @@ +/* + * 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 tech.pegasys.web3signer.signing.KeyType.BLS; + +import tech.pegasys.web3signer.core.Context; +import tech.pegasys.web3signer.core.routes.Web3SignerRoute; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.CommitBoostGenerateProxyKeyHandler; +import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.BlsArtifactSignature; +import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; + +import io.vertx.core.http.HttpMethod; +import io.vertx.core.json.JsonObject; + +public class CommitBoostGenerateProxyKeyRoute implements Web3SignerRoute { + private static final String PATH = "/signer/v1/generate_proxy_key"; + private final Context context; + private final SignerForIdentifier blsSigner; + + public CommitBoostGenerateProxyKeyRoute(final Context context) { + this.context = context; + + // there should be only one DefaultArtifactSignerProvider in eth2 mode + final ArtifactSignerProvider artifactSignerProvider = + context.getArtifactSignerProviders().stream() + .filter(p -> p instanceof DefaultArtifactSignerProvider) + .findFirst() + .orElseThrow(); + + blsSigner = + new SignerForIdentifier<>( + artifactSignerProvider, sig -> sig.getSignatureData().toString(), BLS); + } + + @Override + public void register() { + context + .getRouter() + .route(HttpMethod.POST, PATH) + .blockingHandler(new CommitBoostGenerateProxyKeyHandler(blsSigner), false) + .failureHandler(context.getErrorHandler()) + .failureHandler( + ctx -> { + final int statusCode = ctx.statusCode(); + if (statusCode == 400) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Bad Request") + .encode()); + } else if (statusCode == 404) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Identifier not found.") + .encode()); + } else if (statusCode == 500) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Internal Server Error") + .encode()); + } else { + ctx.next(); // go to global failure handler + } + }); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java index fac7062de..261290049 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java @@ -44,7 +44,11 @@ public void register() { if (statusCode == 500) { ctx.response() .setStatusCode(statusCode) - .end(new JsonObject().put("error", "Internal Server Error").encode()); + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Internal Server 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/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java new file mode 100644 index 000000000..e9c009383 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -0,0 +1,85 @@ +/* + * 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 tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.JSON_UTF_8; +import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier; + +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; + +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.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CommitBoostGenerateProxyKeyHandler implements Handler { + private static final Logger LOG = LogManager.getLogger(); + + private static final ObjectMapper JSON_MAPPER = SigningObjectMapperFactory.createObjectMapper(); + public static final int NOT_FOUND = 404; + public static final int BAD_REQUEST = 400; + private static final int INTERNAL_ERROR = 500; + private final SignerForIdentifier signerForIdentifier; + + public CommitBoostGenerateProxyKeyHandler(final SignerForIdentifier signerForIdentifier) { + this.signerForIdentifier = signerForIdentifier; + } + + @Override + public void handle(final RoutingContext context) { + final String body = context.body().asString(); + + // read and validate incoming json body + final GenerateProxyKeyBody proxyKeyBody; + try { + proxyKeyBody = JSON_MAPPER.readValue(body, GenerateProxyKeyBody.class); + } catch (final JsonProcessingException | IllegalArgumentException e) { + context.fail(BAD_REQUEST); + return; + } + + // Check for identifier, if not exist, fail with 404 + final String identifier = normaliseIdentifier(proxyKeyBody.blsPublicKey()); + if (!signerForIdentifier.isSignerAvailable(identifier)) { + context.fail(NOT_FOUND); + return; + } + + // TODO: Generate actual proxy key based on signature scheme + // TODO: Add generated proxy key to DefaultArtifactSignerProvider + + final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, ""); + + // TODO: Generate actual signature. This involves custom domain and zzs classes + final String signature = ""; // need tree-hash-root of ProxyKeyMessage + + final GenerateProxyKeyResponse generateProxyKeyResponse = + new GenerateProxyKeyResponse(proxyKeyMessage, signature); + + try { + final String jsonEncoded = JSON_MAPPER.writeValueAsString(generateProxyKeyResponse); + context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); + } catch (final JsonProcessingException e) { + // this is not meant to happen + LOG.error("Failed to encode GenerateProxyKeyResponse to JSON", e); + context.fail(INTERNAL_ERROR); + } + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java new file mode 100644 index 000000000..993595223 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java @@ -0,0 +1,19 @@ +/* + * 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; + +public record GenerateProxyKeyBody( + @JsonProperty(value = "pubkey", required = true) String blsPublicKey, + @JsonProperty(value = "scheme", required = true) String ProxyKeySignatureScheme) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java new file mode 100644 index 000000000..5cb518cd1 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java @@ -0,0 +1,19 @@ +/* + * 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; + +public record GenerateProxyKeyResponse( + @JsonProperty(value = "message") ProxyKeyMessage proxyKeyMessage, + @JsonProperty(value = "signature") String signature) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java new file mode 100644 index 000000000..9851700ad --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java @@ -0,0 +1,19 @@ +/* + * 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; + +public record ProxyKeyMessage( + @JsonProperty(value = "delegator", required = true) String blsPublicKey, + @JsonProperty(value = "proxy", required = true) String proxyPublicKey) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeySignatureScheme.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeySignatureScheme.java new file mode 100644 index 000000000..3f33902d9 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeySignatureScheme.java @@ -0,0 +1,18 @@ +/* + * 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; + +public enum ProxyKeySignatureScheme { + BLS, + ECDSA +} From ca5cc03ea4d773acdfa722cf09b15a8b21b3d8c6 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 22 Oct 2024 11:59:19 +1000 Subject: [PATCH 07/57] Generate proxy keys based on scheme --- .../pegasys/web3signer/core/Eth2Runner.java | 2 + .../CommitBoostGenerateProxyKeyRoute.java | 9 +- .../CommitBoostGenerateProxyKeyHandler.java | 34 ++++- .../commitboost/ProxyKeyGenerator.java | 138 ++++++++++++++++++ .../json/GenerateProxyKeyBody.java | 2 +- .../handlers/signing/SignerForIdentifier.java | 9 ++ .../signing/ArtifactSignerProvider.java | 11 ++ .../config/DefaultArtifactSignerProvider.java | 12 ++ 8 files changed, 206 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java 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 dd1a3abdf..f5e510a26 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -25,6 +25,7 @@ import tech.pegasys.web3signer.core.config.BaseConfig; import tech.pegasys.web3signer.core.routes.PublicKeysListRoute; 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.Eth2SignExtensionRoute; import tech.pegasys.web3signer.core.routes.eth2.Eth2SignRoute; @@ -143,6 +144,7 @@ public void populateRouter(final Context context) { } if (commitBoostApiParameters.isEnabled()) { new CommitBoostPublicKeysRoute(context).register(); + new CommitBoostGenerateProxyKeyRoute(context, commitBoostApiParameters).register(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java index 23dd04ace..8b79fc0af 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java @@ -21,6 +21,7 @@ import tech.pegasys.web3signer.signing.ArtifactSignerProvider; import tech.pegasys.web3signer.signing.BlsArtifactSignature; import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; +import tech.pegasys.web3signer.signing.config.KeystoresParameters; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; @@ -29,9 +30,12 @@ public class CommitBoostGenerateProxyKeyRoute implements Web3SignerRoute { private static final String PATH = "/signer/v1/generate_proxy_key"; private final Context context; private final SignerForIdentifier blsSigner; + private final KeystoresParameters commitBoostApiParameters; - public CommitBoostGenerateProxyKeyRoute(final Context context) { + public CommitBoostGenerateProxyKeyRoute( + final Context context, final KeystoresParameters commitBoostApiParameters) { this.context = context; + this.commitBoostApiParameters = commitBoostApiParameters; // there should be only one DefaultArtifactSignerProvider in eth2 mode final ArtifactSignerProvider artifactSignerProvider = @@ -50,7 +54,8 @@ public void register() { context .getRouter() .route(HttpMethod.POST, PATH) - .blockingHandler(new CommitBoostGenerateProxyKeyHandler(blsSigner), false) + .blockingHandler( + new CommitBoostGenerateProxyKeyHandler(blsSigner, commitBoostApiParameters), false) .failureHandler(context.getErrorHandler()) .failureHandler( ctx -> { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index e9c009383..d7489c5ad 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -21,6 +21,8 @@ import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; +import tech.pegasys.web3signer.signing.ArtifactSigner; +import tech.pegasys.web3signer.signing.config.KeystoresParameters; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,15 +33,19 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); - private static final ObjectMapper JSON_MAPPER = SigningObjectMapperFactory.createObjectMapper(); - public static final int NOT_FOUND = 404; - public static final int BAD_REQUEST = 400; + private static final int NOT_FOUND = 404; + private static final int BAD_REQUEST = 400; private static final int INTERNAL_ERROR = 500; + private final SignerForIdentifier signerForIdentifier; + private final ProxyKeyGenerator proxyKeyGenerator; - public CommitBoostGenerateProxyKeyHandler(final SignerForIdentifier signerForIdentifier) { + public CommitBoostGenerateProxyKeyHandler( + final SignerForIdentifier signerForIdentifier, + final KeystoresParameters commitBoostApiParameters) { this.signerForIdentifier = signerForIdentifier; + proxyKeyGenerator = new ProxyKeyGenerator(commitBoostApiParameters); } @Override @@ -62,17 +68,29 @@ public void handle(final RoutingContext context) { return; } - // TODO: Generate actual proxy key based on signature scheme - // TODO: Add generated proxy key to DefaultArtifactSignerProvider - - final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, ""); + // Generate actual proxy key and encrypted keystore based on signature scheme + final ArtifactSigner artifactSigner; + try { + artifactSigner = + switch (proxyKeyBody.scheme()) { + case BLS -> proxyKeyGenerator.generateBLSProxyKey(identifier); + case ECDSA -> proxyKeyGenerator.generateECProxyKey(identifier); + }; + // Add generated proxy key to DefaultArtifactSignerProvider + signerForIdentifier.getSignerProvider().addProxySigner(artifactSigner, identifier).get(); + } catch (final Exception e) { + context.fail(INTERNAL_ERROR, e); + return; + } // TODO: Generate actual signature. This involves custom domain and zzs classes + final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); final String signature = ""; // need tree-hash-root of ProxyKeyMessage final GenerateProxyKeyResponse generateProxyKeyResponse = new GenerateProxyKeyResponse(proxyKeyMessage, signature); + // Encode and send response try { final String jsonEncoded = JSON_MAPPER.writeValueAsString(generateProxyKeyResponse); context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java new file mode 100644 index 000000000..b743149d6 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -0,0 +1,138 @@ +/* + * 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 tech.pegasys.teku.bls.keystore.model.Pbkdf2PseudoRandomFunction.HMAC_SHA256; + +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.bls.keystore.KeyStore; +import tech.pegasys.teku.bls.keystore.KeyStoreLoader; +import tech.pegasys.teku.bls.keystore.model.Cipher; +import tech.pegasys.teku.bls.keystore.model.CipherFunction; +import tech.pegasys.teku.bls.keystore.model.KdfParam; +import tech.pegasys.teku.bls.keystore.model.KeyStoreData; +import tech.pegasys.teku.bls.keystore.model.Pbkdf2Param; +import tech.pegasys.web3signer.signing.ArtifactSigner; +import tech.pegasys.web3signer.signing.BlsArtifactSigner; +import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; +import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.config.metadata.SignerOrigin; +import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes48; +import org.web3j.crypto.Credentials; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Keys; +import org.web3j.crypto.WalletUtils; +import org.web3j.crypto.exception.CipherException; + +/** Proxy Key Generator class to generate proxy keys for CommitBoost API */ +public class ProxyKeyGenerator { + private static final Logger LOG = LogManager.getLogger(); + private final SecureRandom secureRandom = new SecureRandom(); + private final KeystoresParameters commitBoostApiParameters; + + public ProxyKeyGenerator(final KeystoresParameters commitBoostApiParameters) { + this.commitBoostApiParameters = commitBoostApiParameters; + } + + public ArtifactSigner generateECProxyKey(final String identifier) + throws GeneralSecurityException, UncheckedIOException { + final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); + final Path ecWalletFile = createECWalletFile(ecKeyPair, identifier); + LOG.debug("Created proxy EC wallet file {} for identifier: {}", ecWalletFile, identifier); + return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair))); + } + + public ArtifactSigner generateBLSProxyKey(final String identifier) throws UncheckedIOException { + final BLSKeyPair blsKeyPair = BLSKeyPair.random(secureRandom); + final Path blsKeystoreFile = createBLSKeystoreFile(blsKeyPair, identifier); + LOG.debug("Created proxy BLS keystore file {} for identifier: {}", blsKeystoreFile, identifier); + return new BlsArtifactSigner(blsKeyPair, SignerOrigin.FILE_KEYSTORE); + } + + private Path createBLSKeystoreFile(final BLSKeyPair keyPair, final String identifier) { + final Bytes salt = Bytes.random(32, secureRandom); + final Bytes iv = Bytes.random(16, secureRandom); + final int counter = 65536; // 2^16 + final KdfParam kdfParam = new Pbkdf2Param(32, counter, HMAC_SHA256, salt); + final Cipher cipher = new Cipher(CipherFunction.AES_128_CTR, iv); + final Bytes48 publicKey = keyPair.getPublicKey().toBytesCompressed(); + final String password = readFile(commitBoostApiParameters.getKeystoresPasswordFile()); + final KeyStoreData keyStoreData = + KeyStore.encrypt( + keyPair.getSecretKey().toBytes(), publicKey, password, "", kdfParam, cipher); + try { + final Path keystoreDir = + createV4Directory(commitBoostApiParameters.getKeystoresPath(), identifier); + final Path keystoreFile = keystoreDir.resolve(publicKey + ".json"); + KeyStoreLoader.saveToFile(keystoreFile, keyStoreData); + return keystoreFile; + } catch (final IOException e) { + throw new UncheckedIOException("Unable to create keystore file", e); + } + } + + private Path createECWalletFile(final ECKeyPair ecKeyPair, final String identifier) { + final String password = readFile(commitBoostApiParameters.getKeystoresPasswordFile()); + final Path v3Dir = createV3Directory(commitBoostApiParameters.getKeystoresPath(), identifier); + final String fileName; + try { + fileName = WalletUtils.generateWalletFile(password, ecKeyPair, v3Dir.toFile(), true); + } catch (CipherException | IOException e) { + throw new RuntimeException(e); + } + return v3Dir.resolve(fileName); + } + + private static String readFile(final Path file) throws UncheckedIOException { + final String password; + try { + password = Files.readString(file, StandardCharsets.UTF_8); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + return password; + } + + private static Path createV4Directory(final Path parentDirectory, final String directoryName) { + final Path v4Directory = parentDirectory.resolve(directoryName).resolve("v4"); + try { + Files.createDirectories(v4Directory); + } catch (final IOException e) { + throw new UncheckedIOException("Unable to create directory: " + v4Directory, e); + } + return v4Directory; + } + + private static Path createV3Directory(final Path parentDirectory, final String directoryName) { + final Path v3Directory = parentDirectory.resolve(directoryName).resolve("v3"); + try { + Files.createDirectories(v3Directory); + } catch (final IOException e) { + throw new UncheckedIOException("Unable to create directory: " + v3Directory, e); + } + return v3Directory; + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java index 993595223..db4c30470 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyBody.java @@ -16,4 +16,4 @@ public record GenerateProxyKeyBody( @JsonProperty(value = "pubkey", required = true) String blsPublicKey, - @JsonProperty(value = "scheme", required = true) String ProxyKeySignatureScheme) {} + @JsonProperty(value = "scheme", required = true) ProxyKeySignatureScheme scheme) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java index 7148fb6fd..5776805e4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java @@ -56,6 +56,15 @@ public Optional signAndGetArtifactSignature(final String identifier, final By return signerProvider.getSigner(identifier).map(signer -> (T) signer.sign(data)); } + /** + * Get the signer provider + * + * @return signer provider + */ + public ArtifactSignerProvider getSignerProvider() { + return signerProvider; + } + /** * Converts hex string to bytes * diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java index 985100271..2ecefe42d 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java @@ -67,6 +67,17 @@ public interface ArtifactSignerProvider extends Closeable { */ Future removeSigner(final String identifier); + /** + * Add a proxy signer to the signer provider. + * + * @param signer Instance of ArtifactSigner + * @param identifier Identifier of the signer for which proxy signer is being added + * @return a future that completes when the proxy signer is added + */ + default Future addProxySigner(final ArtifactSigner signer, final String identifier) { + throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); + } + /** Close the executor service and release any resources. */ @Override void close(); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 5d0e6f956..448942631 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -160,11 +160,23 @@ public Future removeSigner(final String identifier) { return executorService.submit( () -> { signers.remove(identifier); + proxySigners.remove(identifier); LOG.info("Removed signer with identifier '{}'", identifier); return null; }); } + @Override + public Future addProxySigner(final ArtifactSigner signer, final String identifier) { + return executorService.submit( + () -> { + proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).add(signer); + LOG.info( + "Loaded new proxy signer {} for identifier '{}'", signer.getIdentifier(), identifier); + return null; + }); + } + @Override public void close() { executorService.shutdownNow(); From 62b26a9f78498935f24146c249a746b70d614aa6 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 22 Oct 2024 12:07:41 +1000 Subject: [PATCH 08/57] code fix --- .../commitboost/CommitBoostGenerateProxyKeyHandler.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index d7489c5ad..cf5c770f2 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -84,7 +84,8 @@ public void handle(final RoutingContext context) { } // TODO: Generate actual signature. This involves custom domain and zzs classes - final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); + final ProxyKeyMessage proxyKeyMessage = + new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); final String signature = ""; // need tree-hash-root of ProxyKeyMessage final GenerateProxyKeyResponse generateProxyKeyResponse = From ec91f2e0fa302fdde5aa7e3cb227e96541586c8b Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 22 Oct 2024 12:16:39 +1000 Subject: [PATCH 09/57] skeleton for compute signing root --- .../CommitBoostGenerateProxyKeyHandler.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index cf5c770f2..61b0624fb 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -24,12 +24,15 @@ import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.config.KeystoresParameters; +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.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); @@ -84,12 +87,18 @@ public void handle(final RoutingContext context) { } // TODO: Generate actual signature. This involves custom domain and zzs classes + // TODO: Use actual Bytes/instance of public key for calculating signing root?? final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); - final String signature = ""; // need tree-hash-root of ProxyKeyMessage + final Optional optionalSig = + signerForIdentifier.sign(identifier, computeSigningRoot(proxyKeyMessage)); + if (optionalSig.isEmpty()) { + context.fail(NOT_FOUND); + return; + } final GenerateProxyKeyResponse generateProxyKeyResponse = - new GenerateProxyKeyResponse(proxyKeyMessage, signature); + new GenerateProxyKeyResponse(proxyKeyMessage, optionalSig.get()); // Encode and send response try { @@ -101,4 +110,9 @@ public void handle(final RoutingContext context) { context.fail(INTERNAL_ERROR); } } + + private Bytes computeSigningRoot(final ProxyKeyMessage proxyKeyMessage) { + + return Bytes.EMPTY; // TODO: Fix me + } } From 16277edb4666aadbc19df18976b1bcd5c4676d72 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 09:58:50 +1000 Subject: [PATCH 10/57] Commit Boost - Gen Proxy Key - BLS Signing root --- .../subcommands/Eth2SubCommand.java | 71 ++++++++++++++++--- .../pegasys/web3signer/core/Eth2Runner.java | 7 +- .../CommitBoostGenerateProxyKeyRoute.java | 15 +++- .../CommitBoostGenerateProxyKeyHandler.java | 48 ++++++++++--- .../datastructure/BlsProxyKeyMessage.java | 35 +++++++++ .../datastructure/BlsProxyKeySchema.java | 38 ++++++++++ .../core/util/CommitBoostSigningRootUtil.java | 19 +++++ .../signing/secp256k1/EthPublicKeyUtils.java | 48 +++++++++++++ .../DefaultArtifactSignerProviderTest.java | 7 +- 9 files changed, 266 insertions(+), 22 deletions(-) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java 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 92bf6ef19..f7dd2590b 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 @@ -24,6 +24,8 @@ import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.ForkSchedule; import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.web3signer.commandline.PicoCliAwsSecretsManagerParameters; import tech.pegasys.web3signer.commandline.PicoCliEth2AzureKeyVaultParameters; @@ -37,6 +39,7 @@ import tech.pegasys.web3signer.signing.config.KeystoresParameters; import tech.pegasys.web3signer.slashingprotection.SlashingProtectionParameters; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -45,8 +48,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.HelpCommand; @@ -91,6 +96,15 @@ private static class NetworkCliCompletionCandidates extends ArrayList { arity = "1") private String network; + @CommandLine.Option( + names = {"--genesis-state"}, + paramLabel = "", + description = + "The genesis state. This value should be a file or URL pointing to an SSZ-encoded finalized checkpoint " + + "state.", + arity = "1") + private String genesisState; + @CommandLine.Option( names = {"--Xnetwork-altair-fork-epoch"}, hidden = true, @@ -167,6 +181,7 @@ private static class NetworkCliCompletionCandidates extends ArrayList { @Mixin private PicoCliGcpSecretManagerParameters gcpSecretManagerParameters; @Mixin private PicoCommitBoostApiParameters commitBoostApiParameters; private tech.pegasys.teku.spec.Spec eth2Spec; + private Bytes32 genesisValidatorRoot; public Eth2SubCommand() { network = "mainnet"; @@ -184,6 +199,7 @@ public Runner createRunner() { awsSecretsManagerParameters, gcpSecretManagerParameters, eth2Spec, + genesisValidatorRoot, isKeyManagerApiEnabled, signingExtEnabled, commitBoostApiParameters); @@ -233,20 +249,15 @@ private Eth2NetworkConfiguration createEth2NetworkConfig() { if (trustedSetup != null) { builder.trustedSetup(trustedSetup); } + if (StringUtils.isNotBlank(genesisState)) { + builder.customGenesisState(genesisState); + } return builder.build(); } @Override protected void validateArgs() { - try { - Eth2NetworkConfiguration eth2NetworkConfig = createEth2NetworkConfig(); - eth2Spec = eth2NetworkConfig.getSpec(); - } catch (final IllegalArgumentException e) { - throw new ParameterException( - commandSpec.commandLine(), - "Failed to load network " + network + " due to " + e.getMessage(), - e); - } + validateAndInitGenesisValidatorRoot(validateAndInitSpec()); if (slashingProtectionParameters.isEnabled() && slashingProtectionParameters.getDbUrl() == null) { @@ -267,6 +278,48 @@ protected void validateArgs() { commitBoostApiParameters.validateParameters(); } + private void validateAndInitGenesisValidatorRoot( + final Eth2NetworkConfiguration eth2NetworkConfig) { + final String genesisState = + eth2NetworkConfig + .getNetworkBoostrapConfig() + .getGenesisState() + .orElseThrow( + () -> + new ParameterException( + commandSpec.commandLine(), + "Unable to load genesis state for network " + + network + + ". Please provide a valid genesis state resource.")); + try { + final BeaconState beaconState = ChainDataLoader.loadState(eth2Spec, genesisState); + this.genesisValidatorRoot = beaconState.getGenesisValidatorsRoot(); + } catch (final IOException | NullPointerException e) { + throw new ParameterException( + commandSpec.commandLine(), + "Unable to load genesis state for network " + + network + + " from " + + genesisState + + ": " + + e.getMessage()); + } + } + + private Eth2NetworkConfiguration validateAndInitSpec() { + final Eth2NetworkConfiguration eth2NetworkConfig; + try { + eth2NetworkConfig = createEth2NetworkConfig(); + eth2Spec = eth2NetworkConfig.getSpec(); + } catch (final IllegalArgumentException e) { + throw new ParameterException( + commandSpec.commandLine(), + "Failed to load network " + network + " due to " + e.getMessage(), + e); + } + return eth2NetworkConfig; + } + private void validateGcpSecretManagerParameters() { if (gcpSecretManagerParameters.isEnabled()) { final List specifiedAuthModeMissingFields = 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 f5e510a26..400c3eb7d 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -88,6 +88,7 @@ public class Eth2Runner extends Runner { private final boolean pruningEnabled; private final KeystoresParameters keystoresParameters; private final Spec eth2Spec; + private final Bytes32 genesisValidatorsRoot; private final boolean isKeyManagerApiEnabled; private final boolean signingExtEnabled; private final KeystoresParameters commitBoostApiParameters; @@ -100,6 +101,7 @@ public Eth2Runner( final AwsVaultParameters awsVaultParameters, final GcpSecretManagerParameters gcpSecretManagerParameters, final Spec eth2Spec, + final Bytes32 genesisValidatorsRoot, final boolean isKeyManagerApiEnabled, final boolean signingExtEnabled, final KeystoresParameters commitBoostApiParameters) { @@ -110,6 +112,7 @@ public Eth2Runner( this.pruningEnabled = slashingProtectionParameters.isPruningEnabled(); this.keystoresParameters = keystoresParameters; this.eth2Spec = eth2Spec; + this.genesisValidatorsRoot = genesisValidatorsRoot; this.isKeyManagerApiEnabled = isKeyManagerApiEnabled; this.awsVaultParameters = awsVaultParameters; this.gcpSecretManagerParameters = gcpSecretManagerParameters; @@ -144,7 +147,9 @@ public void populateRouter(final Context context) { } if (commitBoostApiParameters.isEnabled()) { new CommitBoostPublicKeysRoute(context).register(); - new CommitBoostGenerateProxyKeyRoute(context, commitBoostApiParameters).register(); + new CommitBoostGenerateProxyKeyRoute( + context, commitBoostApiParameters, eth2Spec, genesisValidatorsRoot) + .register(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java index 8b79fc0af..6770a4e26 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java @@ -14,6 +14,7 @@ import static tech.pegasys.web3signer.signing.KeyType.BLS; +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.CommitBoostGenerateProxyKeyHandler; @@ -25,17 +26,25 @@ import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; +import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyRoute implements Web3SignerRoute { private static final String PATH = "/signer/v1/generate_proxy_key"; private final Context context; private final SignerForIdentifier blsSigner; private final KeystoresParameters commitBoostApiParameters; + private final Spec eth2Spec; + private final Bytes32 genesisValidatorsRoot; public CommitBoostGenerateProxyKeyRoute( - final Context context, final KeystoresParameters commitBoostApiParameters) { + final Context context, + final KeystoresParameters commitBoostApiParameters, + final Spec eth2Spec, + final Bytes32 genesisValidatorsRoot) { this.context = context; this.commitBoostApiParameters = commitBoostApiParameters; + this.eth2Spec = eth2Spec; + this.genesisValidatorsRoot = genesisValidatorsRoot; // there should be only one DefaultArtifactSignerProvider in eth2 mode final ArtifactSignerProvider artifactSignerProvider = @@ -55,7 +64,9 @@ public void register() { .getRouter() .route(HttpMethod.POST, PATH) .blockingHandler( - new CommitBoostGenerateProxyKeyHandler(blsSigner, commitBoostApiParameters), false) + new CommitBoostGenerateProxyKeyHandler( + blsSigner, commitBoostApiParameters, eth2Spec, genesisValidatorsRoot), + false) .failureHandler(context.getErrorHandler()) .failureHandler( ctx -> { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 61b0624fb..489c8db60 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -16,11 +16,19 @@ 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.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.spec.Spec; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeySchema; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; +import tech.pegasys.web3signer.core.util.CommitBoostSigningRootUtil; +import tech.pegasys.web3signer.core.util.DepositSigningRootUtil; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -33,6 +41,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); @@ -43,12 +52,18 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler signerForIdentifier; private final ProxyKeyGenerator proxyKeyGenerator; + private final Spec eth2Spec; + private final Bytes32 genesisValidatorsRoot; public CommitBoostGenerateProxyKeyHandler( final SignerForIdentifier signerForIdentifier, - final KeystoresParameters commitBoostApiParameters) { + final KeystoresParameters commitBoostApiParameters, + final Spec eth2Spec, + final Bytes32 genesisValidatorsRoot) { this.signerForIdentifier = signerForIdentifier; - proxyKeyGenerator = new ProxyKeyGenerator(commitBoostApiParameters); + this.proxyKeyGenerator = new ProxyKeyGenerator(commitBoostApiParameters); + this.eth2Spec = eth2Spec; + this.genesisValidatorsRoot = genesisValidatorsRoot; } @Override @@ -86,12 +101,11 @@ public void handle(final RoutingContext context) { return; } - // TODO: Generate actual signature. This involves custom domain and zzs classes - // TODO: Use actual Bytes/instance of public key for calculating signing root?? final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); final Optional optionalSig = - signerForIdentifier.sign(identifier, computeSigningRoot(proxyKeyMessage)); + signerForIdentifier.sign( + identifier, computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme())); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); return; @@ -111,8 +125,26 @@ public void handle(final RoutingContext context) { } } - private Bytes computeSigningRoot(final ProxyKeyMessage proxyKeyMessage) { - - return Bytes.EMPTY; // TODO: Fix me + private Bytes computeSigningRoot( + final ProxyKeyMessage proxyKeyMessage, final ProxyKeySignatureScheme scheme) { + final Bytes4 genesisForkVersion = eth2Spec.getGenesisSpec().getConfig().getGenesisForkVersion(); + + final Bytes32 domain = + DepositSigningRootUtil.computeDomain( + CommitBoostSigningRootUtil.COMMIT_BOOST_DOMAIN, + genesisForkVersion, + genesisValidatorsRoot); + + final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyKeyMessage.blsPublicKey()); + if (scheme == ProxyKeySignatureScheme.BLS) { + final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyKeyMessage.proxyPublicKey()); + final BlsProxyKeyMessage blsProxyKeyMessage = + new BlsProxyKeySchema().create(delegator, proxy); + + return DepositSigningRootUtil.computeSigningRoot(blsProxyKeyMessage, domain); + } else { + // TODO: Implement ECDSA signing root + throw new UnsupportedOperationException("ECDSA signing root not implemented"); + } } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java new file mode 100644 index 000000000..1ba1fdead --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java @@ -0,0 +1,35 @@ +/* + * 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.datastructure; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; + +public class BlsProxyKeyMessage extends Container2 { + + public BlsProxyKeyMessage( + final BlsProxyKeySchema schema, final BLSPublicKey delegator, final BLSPublicKey proxy) { + super(schema, new SszPublicKey(delegator), new SszPublicKey(proxy)); + } + + BlsProxyKeyMessage(final BlsProxyKeySchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + @Override + public BlsProxyKeySchema getSchema() { + return (BlsProxyKeySchema) super.getSchema(); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java new file mode 100644 index 000000000..5b42fc0e2 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java @@ -0,0 +1,38 @@ +/* + * 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.datastructure; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; + +public class BlsProxyKeySchema + extends ContainerSchema2 { + public BlsProxyKeySchema() { + super( + "BlsProxyKeyMessage", + namedSchema("delegator", SszPublicKeySchema.INSTANCE), + namedSchema("proxy", SszPublicKeySchema.INSTANCE)); + } + + public BlsProxyKeyMessage create(final BLSPublicKey delegator, final BLSPublicKey proxy) { + return new BlsProxyKeyMessage(this, delegator, proxy); + } + + @Override + public BlsProxyKeyMessage createFromBackingNode(TreeNode treeNode) { + return new BlsProxyKeyMessage(this, treeNode); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java b/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java new file mode 100644 index 000000000..66452c97f --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java @@ -0,0 +1,19 @@ +/* + * 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.util; + +import tech.pegasys.teku.infrastructure.bytes.Bytes4; + +public class CommitBoostSigningRootUtil { + public static final Bytes4 COMMIT_BOOST_DOMAIN = Bytes4.fromHexString("0x6d6d6f43"); +} diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 2ae294201..4be128891 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -17,8 +17,13 @@ import java.math.BigInteger; import java.security.AlgorithmParameters; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; @@ -29,10 +34,23 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.web3j.utils.Numeric; public class EthPublicKeyUtils { private static final int PUBLIC_KEY_SIZE = 64; + private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); + private static final ECDomainParameters SECP256K1_DOMAIN_PARAMS; + + static { + final ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1"); + SECP256K1_DOMAIN_PARAMS = + new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + } public static ECPublicKey createPublicKey(final ECPoint publicPoint) { try { @@ -71,4 +89,34 @@ public static byte[] toByteArray(final ECPublicKey publicKey) { public static String toHexString(final ECPublicKey publicKey) { return Bytes.wrap(toByteArray(publicKey)).toHexString(); } + + public static Bytes getEncoded(final ECPublicKey publicKey, boolean encodeCompressed) { + // Check if it's already a Bouncy Castle ECPublicKey + if (publicKey instanceof BCECPublicKey) { + return Bytes.wrap(((BCECPublicKey) publicKey).getQ().getEncoded(encodeCompressed)); + } + + // Convert java.security.spec.ECPoint to org.bouncycastle.math.ec.ECPoint + java.security.spec.ECPoint javaECPoint = publicKey.getW(); + org.bouncycastle.math.ec.ECPoint bcEcPoint = + SECP256K1_DOMAIN_PARAMS + .getCurve() + .createPoint(javaECPoint.getAffineX(), javaECPoint.getAffineY()); + + // Return the encoding + return Bytes.wrap(bcEcPoint.getEncoded(encodeCompressed)); + } + + public static KeyPair createSecp256k1KeyPair(final SecureRandom random) + throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { + final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BC_PROVIDER); + final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("secp256k1"); + if (random != null) { + keyPairGenerator.initialize(ecGenParameterSpec, random); + } else { + keyPairGenerator.initialize(ecGenParameterSpec); + } + + return keyPairGenerator.generateKeyPair(); + } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 2c8043f20..3a697e830 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -29,6 +29,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.security.GeneralSecurityException; +import java.security.KeyPair; import java.security.SecureRandom; import java.util.List; import java.util.Map; @@ -39,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.web3j.crypto.ECKeyPair; -import org.web3j.crypto.Keys; import org.web3j.crypto.WalletUtils; import org.web3j.crypto.exception.CipherException; @@ -203,7 +203,10 @@ private List randomSecpV3Keystores( .mapToObj( i -> { try { - final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); + final KeyPair secp256k1KeyPair = + EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom); + final ECKeyPair ecKeyPair = ECKeyPair.create(secp256k1KeyPair); + WalletUtils.generateWalletFile("password", ecKeyPair, v3Dir.toFile(), false); return ecKeyPair; } catch (GeneralSecurityException | CipherException | IOException e) { From 5267e714e369c460e543ce08b7e14b0a45c1c915 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 16:58:44 +1000 Subject: [PATCH 11/57] Rewrite EthPublicKeyUtils to use compressed and uncompressed public keys --- .../signing/secp256k1/EthPublicKeyUtils.java | 204 ++++++++++++------ .../secp256k1/EthPublicKeyUtilsTest.java | 132 ++++++++++-- 2 files changed, 259 insertions(+), 77 deletions(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 4be128891..8f326e701 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -12,111 +12,185 @@ */ package tech.pegasys.web3signer.signing.secp256k1; -import static com.google.common.base.Preconditions.checkArgument; -import static org.bouncycastle.util.BigIntegers.asUnsignedByteArray; - import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.InvalidAlgorithmParameterException; +import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; +import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; +import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; -import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; -import org.web3j.utils.Numeric; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; public class EthPublicKeyUtils { - private static final int PUBLIC_KEY_SIZE = 64; private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); - private static final ECDomainParameters SECP256K1_DOMAIN_PARAMS; + private static final ECDomainParameters SECP256K1_DOMAIN; + private static final ECParameterSpec SECP256K1_SPEC; + private static final java.security.spec.ECParameterSpec JAVA_SECP256K1_PARAM_SPEC; + private static final String SECP256K1_CURVE = "secp256k1"; + private static final String EC_ALGORITHM = "EC"; static { - final ECNamedCurveParameterSpec params = ECNamedCurveTable.getParameterSpec("secp256k1"); - SECP256K1_DOMAIN_PARAMS = + final X9ECParameters params = CustomNamedCurves.getByName(SECP256K1_CURVE); + SECP256K1_DOMAIN = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); + SECP256K1_SPEC = + new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); + final ECCurve bcCurve = SECP256K1_SPEC.getCurve(); + JAVA_SECP256K1_PARAM_SPEC = + new java.security.spec.ECParameterSpec( + new EllipticCurve( + new java.security.spec.ECFieldFp(bcCurve.getField().getCharacteristic()), + bcCurve.getA().toBigInteger(), + bcCurve.getB().toBigInteger()), + new java.security.spec.ECPoint( + SECP256K1_SPEC.getG().getAffineXCoord().toBigInteger(), + SECP256K1_SPEC.getG().getAffineYCoord().toBigInteger()), + SECP256K1_SPEC.getN(), + SECP256K1_SPEC.getH().intValue()); } - public static ECPublicKey createPublicKey(final ECPoint publicPoint) { - try { - final AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC"); - parameters.init(new ECGenParameterSpec("secp256k1")); - final ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); - final ECPublicKeySpec pubSpec = new ECPublicKeySpec(publicPoint, ecParameters); - final KeyFactory kf = KeyFactory.getInstance("EC"); - return (ECPublicKey) kf.generatePublic(pubSpec); - } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { - throw new IllegalStateException("Unable to create Ethereum public key", e); + /** + * Create a new secp256k1 key pair. + * + * @param random The random number generator to use + * @return The generated key pair + * @throws GeneralSecurityException If there is an issue generating the key pair + */ + public static KeyPair createSecp256k1KeyPair(final SecureRandom random) + throws GeneralSecurityException { + final KeyPairGenerator keyPairGenerator = + KeyPairGenerator.getInstance(EC_ALGORITHM, BC_PROVIDER); + final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(SECP256K1_CURVE); + if (random != null) { + keyPairGenerator.initialize(ecGenParameterSpec, random); + } else { + keyPairGenerator.initialize(ecGenParameterSpec); } + + return keyPairGenerator.generateKeyPair(); } public static ECPublicKey createPublicKey(final Bytes value) { - checkArgument(value.size() == PUBLIC_KEY_SIZE, "Invalid public key size must be 64 bytes"); - final Bytes x = value.slice(0, 32); - final Bytes y = value.slice(32, 32); - final ECPoint ecPoint = - new ECPoint(Numeric.toBigInt(x.toArrayUnsafe()), Numeric.toBigInt(y.toArrayUnsafe())); - return createPublicKey(ecPoint); + if (value.size() != 33 && value.size() != 65 && value.size() != 64) { + throw new IllegalArgumentException( + "Invalid public key length. Expected 33, 64, or 65 bytes."); + } + + final ECPoint point; + if (value.size() == 64) { + // For 64-byte input, we need to prepend the 0x04 prefix for uncompressed format + byte[] fullKey = new byte[65]; + fullKey[0] = 0x04; + System.arraycopy(value.toArrayUnsafe(), 0, fullKey, 1, 64); + point = SECP256K1_DOMAIN.getCurve().decodePoint(fullKey); + } else { + point = SECP256K1_DOMAIN.getCurve().decodePoint(value.toArrayUnsafe()); + } + + return createPublicKeyFromPoint(point); } - public static ECPublicKey createPublicKey(final BigInteger value) { - final Bytes ethBytes = Bytes.wrap(Numeric.toBytesPadded(value, PUBLIC_KEY_SIZE)); - return createPublicKey(ethBytes); + private static ECPublicKey createPublicKeyFromPoint(final ECPoint point) { + try { + // Convert Bouncy Castle ECPoint to Java ECPoint + final java.security.spec.ECPoint ecPoint = + new java.security.spec.ECPoint( + point.getAffineXCoord().toBigInteger(), point.getAffineYCoord().toBigInteger()); + + final java.security.spec.ECPublicKeySpec pubSpec = + new java.security.spec.ECPublicKeySpec(ecPoint, JAVA_SECP256K1_PARAM_SPEC); + return (ECPublicKey) + KeyFactory.getInstance(EC_ALGORITHM, BC_PROVIDER).generatePublic(pubSpec); + } catch (final InvalidKeySpecException | NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Unable to create EC public key", e); + } } + /** + * Create an ECPublicKey from a BigInteger representation of the public key. + * + * @param publicKeyValue The BigInteger representation of the public key (64 bytes, without + * prefix) + * @return The created ECPublicKey + * @throws IllegalArgumentException if the input is invalid + */ + public static ECPublicKey createPublicKey(final BigInteger publicKeyValue) { + if (publicKeyValue == null) { + throw new IllegalArgumentException("Public key value cannot be null"); + } + + byte[] publicKeyBytes = publicKeyValue.toByteArray(); + + // Ensure we have exactly 64 bytes + if (publicKeyBytes.length < 64) { + byte[] temp = new byte[64]; + System.arraycopy(publicKeyBytes, 0, temp, 64 - publicKeyBytes.length, publicKeyBytes.length); + publicKeyBytes = temp; + } else if (publicKeyBytes.length > 64) { + publicKeyBytes = + Arrays.copyOfRange(publicKeyBytes, publicKeyBytes.length - 64, publicKeyBytes.length); + } + + // Create a new byte array with the uncompressed prefix + byte[] fullPublicKeyBytes = new byte[65]; + fullPublicKeyBytes[0] = 0x04; // Uncompressed point prefix + System.arraycopy(publicKeyBytes, 0, fullPublicKeyBytes, 1, 64); + + // Use the existing createPublicKey method + return createPublicKey(Bytes.wrap(fullPublicKeyBytes)); + } + + @Deprecated // Use getEncoded public static byte[] toByteArray(final ECPublicKey publicKey) { - final ECPoint ecPoint = publicKey.getW(); - final Bytes xBytes = Bytes32.wrap(asUnsignedByteArray(32, ecPoint.getAffineX())); - final Bytes yBytes = Bytes32.wrap(asUnsignedByteArray(32, ecPoint.getAffineY())); - return Bytes.concatenate(xBytes, yBytes).toArray(); + return getEncoded(publicKey, false, false).toArrayUnsafe(); } + @Deprecated // Use getEncoded public static String toHexString(final ECPublicKey publicKey) { - return Bytes.wrap(toByteArray(publicKey)).toHexString(); + return getEncoded(publicKey, false, false).toHexString(); } - public static Bytes getEncoded(final ECPublicKey publicKey, boolean encodeCompressed) { - // Check if it's already a Bouncy Castle ECPublicKey + /** + * Convert java ECPublicKey to Bytes. + * + * @param publicKey The public key to convert + * @param compressed Whether to return the compressed form + * @param withPrefix Whether to include the prefix byte for uncompressed keys + * @return The encoded public key + */ + public static Bytes getEncoded( + final ECPublicKey publicKey, boolean compressed, boolean withPrefix) { + final ECPoint point; if (publicKey instanceof BCECPublicKey) { - return Bytes.wrap(((BCECPublicKey) publicKey).getQ().getEncoded(encodeCompressed)); + // If it's already a Bouncy Castle key, we can get the ECPoint directly + point = ((BCECPublicKey) publicKey).getQ(); + } else { + // If it's not a BC key, we need to create the ECPoint from the coordinates + final BigInteger x = publicKey.getW().getAffineX(); + final BigInteger y = publicKey.getW().getAffineY(); + point = SECP256K1_SPEC.getCurve().createPoint(x, y); } - // Convert java.security.spec.ECPoint to org.bouncycastle.math.ec.ECPoint - java.security.spec.ECPoint javaECPoint = publicKey.getW(); - org.bouncycastle.math.ec.ECPoint bcEcPoint = - SECP256K1_DOMAIN_PARAMS - .getCurve() - .createPoint(javaECPoint.getAffineX(), javaECPoint.getAffineY()); - - // Return the encoding - return Bytes.wrap(bcEcPoint.getEncoded(encodeCompressed)); - } - - public static KeyPair createSecp256k1KeyPair(final SecureRandom random) - throws NoSuchProviderException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { - final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", BC_PROVIDER); - final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec("secp256k1"); - if (random != null) { - keyPairGenerator.initialize(ecGenParameterSpec, random); + if (compressed) { + return Bytes.wrap(point.getEncoded(true)); + } else if (withPrefix) { + return Bytes.wrap(point.getEncoded(false)); } else { - keyPairGenerator.initialize(ecGenParameterSpec); + return Bytes.wrap(point.getEncoded(false), 1, 64); } - - return keyPairGenerator.generateKeyPair(); } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index c75cfb9a8..2a6b5abfa 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -14,22 +14,31 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.assertj.core.api.Fail.fail; import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; +import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.math.ec.ECFieldElement; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; import org.web3j.utils.Numeric; @@ -41,15 +50,6 @@ class EthPublicKeyUtilsTest { "0xaf80b90d25145da28c583359beb47b21796b2fe1a23c1511e443e7a64dfdb27d7434c380f0aa4c500e220aa1a9d068514b1ff4d5019e624e7ba1efe82b340a59"; private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1"); - @Test - public void createsPublicKeyFromECPoint() { - final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPoint expectedEcPoint = createEcPoint(publicKeyBytes); - - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(expectedEcPoint); - verifyPublicKey(ecPublicKey, publicKeyBytes, expectedEcPoint); - } - @Test public void createsPublicKeyFromBytes() { final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); @@ -69,12 +69,58 @@ public void createsPublicKeyFromBigInteger() { verifyPublicKey(ecPublicKey, expectedPublicKeyBytes, expectedEcPoint); } + private static Stream validPublicKeys() { + KeyPair keyPair = null; + try { + keyPair = EthPublicKeyUtils.createSecp256k1KeyPair(new SecureRandom()); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + return Stream.of( + // Compressed (33 bytes) + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), true, true), + // Uncompressed without prefix (64 bytes) + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false, false), + // Uncompressed with prefix (65 bytes) + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false, true)); + } + + @ParameterizedTest + @MethodSource("validPublicKeys") + void acceptsValidPublicKeySizes(final Bytes publicKey) { + assertThatCode(() -> EthPublicKeyUtils.createPublicKey(publicKey)).doesNotThrowAnyException(); + } + @ParameterizedTest - @ValueSource(ints = {0, 63, 65}) - public void throwsInvalidArgumentExceptionForInvalidPublicKeySize(final int size) { + @ValueSource(ints = {0, 32, 34, 63, 66}) + void throwsIllegalArgumentExceptionForInvalidPublicKeySize(final int size) { assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(Bytes.random(size))) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid public key size must be 64 bytes"); + .hasMessage("Invalid public key length. Expected 33, 64, or 65 bytes."); + } + + @Test + void throwsIllegalArgumentExceptionForInvalid33ByteKey() { + Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(32)); + assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidCompressedKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Incorrect length for infinity encoding"); + } + + @Test + void throwsIllegalArgumentExceptionForInvalid65ByteKey() { + Bytes invalidUncompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(64)); + assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidUncompressedKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Incorrect length for infinity encoding"); + } + + @Test + void throwsIllegalArgumentExceptionForInvalidCompressedKeyPrefix() { + Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x04), Bytes.random(32)); + assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidCompressedKey)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Incorrect length for uncompressed encoding"); } @Test @@ -97,8 +143,70 @@ public void publicKeyIsConvertedToEthBytes() { assertThat(bytes.get(0)).isNotEqualTo(0x4); } + @Test + public void encodePublicKey() throws GeneralSecurityException { + final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); + + final Bytes uncompressedWithoutPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); + final Bytes uncompressedWithPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, true); + final Bytes compressed = EthPublicKeyUtils.getEncoded(ecPublicKey, true, true); + + assertThat(uncompressedWithoutPrefix.size()).isEqualTo(64); + assertThat(uncompressedWithPrefix.size()).isEqualTo(65); + assertThat(compressed.size()).isEqualTo(33); + } + private void verifyPublicKey( final ECPublicKey ecPublicKey, final Bytes publicKeyBytes, final ECPoint ecPoint) { + // verify public point + assertThat(ecPublicKey.getW()).isEqualTo(ecPoint); + + // verify algorithm + assertThat(ecPublicKey.getAlgorithm()).isEqualTo("EC"); + + // verify curve parameters + final ECParameterSpec params = ecPublicKey.getParams(); + assertThat(params.getCofactor()).isEqualTo(CURVE_PARAMS.getCurve().getCofactor().intValue()); + assertThat(params.getOrder()).isEqualTo(CURVE_PARAMS.getN()); + assertThat(params.getGenerator().getAffineX()) + .isEqualTo(CURVE_PARAMS.getG().getAffineXCoord().toBigInteger()); + assertThat(params.getGenerator().getAffineY()) + .isEqualTo(CURVE_PARAMS.getG().getAffineYCoord().toBigInteger()); + assertThat(params.getCurve().getA()).isEqualTo(CURVE_PARAMS.getCurve().getA().toBigInteger()); + assertThat(params.getCurve().getB()).isEqualTo(CURVE_PARAMS.getCurve().getB().toBigInteger()); + assertThat(params.getCurve().getField().getFieldSize()).isEqualTo(256); + + // Verify format + assertThat(ecPublicKey.getFormat()).isEqualTo("X.509"); + + // Verify encoded form + SubjectPublicKeyInfo subjectPublicKeyInfo = + SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(ecPublicKey.getEncoded())); + assertThat(subjectPublicKeyInfo.getPublicKeyData().getBytes()) + .isEqualTo(Bytes.concatenate(Bytes.of(0x4), publicKeyBytes).toArray()); + + // Verify algorithm identifier + final AlgorithmIdentifier algorithm = subjectPublicKeyInfo.getAlgorithm(); + assertThat(algorithm.getAlgorithm().getId()).isEqualTo(EC_OID); + + // Verify curve identifier + X962Parameters x962Params = X962Parameters.getInstance(algorithm.getParameters()); + if (x962Params.isNamedCurve()) { + assertThat(x962Params.getParameters()).isEqualTo(new ASN1ObjectIdentifier(SECP_OID)); + } else if (x962Params.isImplicitlyCA()) { + fail("Implicitly CA parameters are not expected for secp256k1"); + } else { + X9ECParameters ecParams = X9ECParameters.getInstance(x962Params.getParameters()); + assertThat(ecParams.getCurve()).isEqualTo(CURVE_PARAMS.getCurve()); + assertThat(ecParams.getG()).isEqualTo(CURVE_PARAMS.getG()); + assertThat(ecParams.getN()).isEqualTo(CURVE_PARAMS.getN()); + assertThat(ecParams.getH()).isEqualTo(CURVE_PARAMS.getH()); + } + } + + private void verifyPublicKeyOld( + final ECPublicKey ecPublicKey, final Bytes publicKeyBytes, final ECPoint ecPoint) { assertThat(ecPublicKey.getW()).isEqualTo(ecPoint); assertThat(ecPublicKey.getAlgorithm()).isEqualTo("EC"); From 20daaad6158f0799fb2e4a23e82edf123aaeee84 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 18:27:08 +1000 Subject: [PATCH 12/57] Use updated EthPublicKeyUtils methods --- .../signing/secp256k1/EthPublicKeyUtils.java | 48 ++++++++++++------- .../secp256k1/util/Eth1SignatureUtil.java | 3 +- .../secp256k1/EthPublicKeyUtilsTest.java | 42 +--------------- .../secp256k1/aws/AwsKmsSignerTest.java | 3 +- .../azure/AzureKeyVaultSignerTest.java | 2 +- 5 files changed, 36 insertions(+), 62 deletions(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 8f326e701..fad49d559 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -38,8 +38,8 @@ public class EthPublicKeyUtils { private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); private static final ECDomainParameters SECP256K1_DOMAIN; - private static final ECParameterSpec SECP256K1_SPEC; - private static final java.security.spec.ECParameterSpec JAVA_SECP256K1_PARAM_SPEC; + private static final ECParameterSpec BC_SECP256K1_SPEC; + private static final java.security.spec.ECParameterSpec JAVA_SECP256K1_SPEC; private static final String SECP256K1_CURVE = "secp256k1"; private static final String EC_ALGORITHM = "EC"; @@ -47,20 +47,20 @@ public class EthPublicKeyUtils { final X9ECParameters params = CustomNamedCurves.getByName(SECP256K1_CURVE); SECP256K1_DOMAIN = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); - SECP256K1_SPEC = + BC_SECP256K1_SPEC = new ECParameterSpec(params.getCurve(), params.getG(), params.getN(), params.getH()); - final ECCurve bcCurve = SECP256K1_SPEC.getCurve(); - JAVA_SECP256K1_PARAM_SPEC = + final ECCurve bcCurve = BC_SECP256K1_SPEC.getCurve(); + JAVA_SECP256K1_SPEC = new java.security.spec.ECParameterSpec( new EllipticCurve( new java.security.spec.ECFieldFp(bcCurve.getField().getCharacteristic()), bcCurve.getA().toBigInteger(), bcCurve.getB().toBigInteger()), new java.security.spec.ECPoint( - SECP256K1_SPEC.getG().getAffineXCoord().toBigInteger(), - SECP256K1_SPEC.getG().getAffineYCoord().toBigInteger()), - SECP256K1_SPEC.getN(), - SECP256K1_SPEC.getH().intValue()); + BC_SECP256K1_SPEC.getG().getAffineXCoord().toBigInteger(), + BC_SECP256K1_SPEC.getG().getAffineYCoord().toBigInteger()), + BC_SECP256K1_SPEC.getN(), + BC_SECP256K1_SPEC.getH().intValue()); } /** @@ -112,7 +112,7 @@ private static ECPublicKey createPublicKeyFromPoint(final ECPoint point) { point.getAffineXCoord().toBigInteger(), point.getAffineYCoord().toBigInteger()); final java.security.spec.ECPublicKeySpec pubSpec = - new java.security.spec.ECPublicKeySpec(ecPoint, JAVA_SECP256K1_PARAM_SPEC); + new java.security.spec.ECPublicKeySpec(ecPoint, JAVA_SECP256K1_SPEC); return (ECPublicKey) KeyFactory.getInstance(EC_ALGORITHM, BC_PROVIDER).generatePublic(pubSpec); } catch (final InvalidKeySpecException | NoSuchAlgorithmException e) { @@ -154,16 +154,30 @@ public static ECPublicKey createPublicKey(final BigInteger publicKeyValue) { return createPublicKey(Bytes.wrap(fullPublicKeyBytes)); } - @Deprecated // Use getEncoded - public static byte[] toByteArray(final ECPublicKey publicKey) { - return getEncoded(publicKey, false, false).toArrayUnsafe(); - } - - @Deprecated // Use getEncoded + /** + * Convert a java ECPublicKey to an uncompressed (64 bytes) hex string. + * + * @param publicKey The public key to convert + * @return The public key as a hex string + */ public static String toHexString(final ECPublicKey publicKey) { return getEncoded(publicKey, false, false).toHexString(); } + /** + * Convert a java ECPublicKey to a BigInteger. + * + * @param publicKey The public key to convert + * @return The public key as a BigInteger + */ + public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { + // Get the uncompressed public key without prefix (64 bytes) + final Bytes publicKeyBytes = EthPublicKeyUtils.getEncoded(publicKey, false, false); + + // Convert to BigInteger + return new BigInteger(1, publicKeyBytes.toArrayUnsafe()); + } + /** * Convert java ECPublicKey to Bytes. * @@ -182,7 +196,7 @@ public static Bytes getEncoded( // If it's not a BC key, we need to create the ECPoint from the coordinates final BigInteger x = publicKey.getW().getAffineX(); final BigInteger y = publicKey.getW().getAffineY(); - point = SECP256K1_SPEC.getCurve().createPoint(x, y); + point = BC_SECP256K1_SPEC.getCurve().createPoint(x, y); } if (compressed) { diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java index 0e81f85d9..f17dafeb9 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/util/Eth1SignatureUtil.java @@ -28,7 +28,6 @@ import org.bouncycastle.asn1.DLSequence; import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.Sign; -import org.web3j.utils.Numeric; public class Eth1SignatureUtil { private static final Logger LOG = LogManager.getLogger(); @@ -92,7 +91,7 @@ private static Signature deriveSignature( private static int recoverKeyIndex( final ECPublicKey ecPublicKey, final ECDSASignature sig, final byte[] hash) { - final BigInteger publicKey = Numeric.toBigInt(EthPublicKeyUtils.toByteArray(ecPublicKey)); + final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(ecPublicKey); for (int i = 0; i < 4; i++) { final BigInteger k = Sign.recoverFromSignature(i, sig, hash); LOG.trace("recovered key: {}", k); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index 2a6b5abfa..e4889fe0a 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -27,7 +27,6 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; @@ -35,7 +34,6 @@ import org.bouncycastle.asn1.x9.X962Parameters; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.ec.CustomNamedCurves; -import org.bouncycastle.math.ec.ECFieldElement; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -137,14 +135,14 @@ public void publicKeyIsConvertedToEthBytes() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); - final Bytes bytes = Bytes.wrap(EthPublicKeyUtils.toByteArray(ecPublicKey)); + final Bytes bytes = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); assertThat(bytes).isEqualTo(publicKeyBytes); assertThat(bytes.size()).isEqualTo(64); assertThat(bytes.get(0)).isNotEqualTo(0x4); } @Test - public void encodePublicKey() throws GeneralSecurityException { + public void encodePublicKey() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); @@ -205,45 +203,9 @@ private void verifyPublicKey( } } - private void verifyPublicKeyOld( - final ECPublicKey ecPublicKey, final Bytes publicKeyBytes, final ECPoint ecPoint) { - assertThat(ecPublicKey.getW()).isEqualTo(ecPoint); - assertThat(ecPublicKey.getAlgorithm()).isEqualTo("EC"); - - final ECParameterSpec params = ecPublicKey.getParams(); - assertThat(params.getCofactor()).isEqualTo(CURVE_PARAMS.getCurve().getCofactor().intValue()); - assertThat(params.getOrder()).isEqualTo(CURVE_PARAMS.getCurve().getOrder()); - assertThat(params.getGenerator()).isEqualTo(fromBouncyCastleECPoint(CURVE_PARAMS.getG())); - - assertThat(ecPublicKey.getFormat()).isEqualTo("X.509"); - - SubjectPublicKeyInfo subjectPublicKeyInfo = - SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(ecPublicKey.getEncoded())); - assertThat(subjectPublicKeyInfo.getPublicKeyData().getBytes()) - .isEqualTo(Bytes.concatenate(Bytes.of(0x4), publicKeyBytes).toArray()); - - final AlgorithmIdentifier algorithm = subjectPublicKeyInfo.getAlgorithm(); - assertThat(algorithm.getAlgorithm().getId()).isEqualTo(EC_OID); - assertThat(algorithm.getParameters().toASN1Primitive().toString()).isEqualTo(SECP_OID); - } - private ECPoint createEcPoint(final Bytes publicKeyBytes) { final Bytes x = publicKeyBytes.slice(0, 32); final Bytes y = publicKeyBytes.slice(32, 32); return new ECPoint(Numeric.toBigInt(x.toArrayUnsafe()), Numeric.toBigInt(y.toArrayUnsafe())); } - - private ECPoint fromBouncyCastleECPoint( - final org.bouncycastle.math.ec.ECPoint bouncyCastleECPoint) { - final ECFieldElement xCoord = bouncyCastleECPoint.getAffineXCoord(); - final ECFieldElement yCoord = bouncyCastleECPoint.getAffineYCoord(); - - final Bytes32 xEncoded = Bytes32.wrap(xCoord.getEncoded()); - final Bytes32 yEncoded = Bytes32.wrap(yCoord.getEncoded()); - - final BigInteger x = xEncoded.toUnsignedBigInteger(); - final BigInteger y = yEncoded.toUnsignedBigInteger(); - - return new ECPoint(x, y); - } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java index 527c3e854..5e7dd5d12 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/aws/AwsKmsSignerTest.java @@ -116,8 +116,7 @@ void awsSignatureCanBeVerified() throws SignatureException { final Signer signer = new AwsKmsSignerFactory(cachedAwsKmsClientFactory, applySha3Hash) .createSigner(awsKmsMetadata); - final BigInteger publicKey = - Numeric.toBigInt(EthPublicKeyUtils.toByteArray(signer.getPublicKey())); + final BigInteger publicKey = EthPublicKeyUtils.ecPublicKeyToBigInteger(signer.getPublicKey()); final byte[] dataToSign = "Hello".getBytes(UTF_8); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java index 3fa551e11..c9549088a 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSignerTest.java @@ -71,7 +71,7 @@ void azureSignerCanSign() throws SignatureException { new AzureKeyVaultSignerFactory(new AzureKeyVaultFactory(), new AzureHttpClientFactory()) .createSigner(config); final BigInteger publicKey = - Numeric.toBigInt(EthPublicKeyUtils.toByteArray(azureNonHashedDataSigner.getPublicKey())); + EthPublicKeyUtils.ecPublicKeyToBigInteger(azureNonHashedDataSigner.getPublicKey()); final byte[] dataToSign = "Hello World".getBytes(UTF_8); From 92108da4283020451d885b6b401a88d411832b6c Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 18:50:49 +1000 Subject: [PATCH 13/57] EthSecpArtifactSigner to use compressed identifier or public key --- ...SecpV3KeystoresBulkLoadAcceptanceTest.java | 3 +- .../publickeys/YubiHsmKeysAcceptanceTest.java | 2 +- .../signing/SecpSigningAcceptanceTest.java | 3 +- .../commitboost/ProxyKeyGenerator.java | 2 +- .../EthSignTransactionResultProviderTest.java | 6 +- .../signing/EthSecpArtifactSigner.java | 12 ++- .../SecpV3KeystoresBulkLoader.java | 11 ++- .../config/DefaultArtifactSignerProvider.java | 73 ++++++++++--------- .../signing/secp256k1/EthPublicKeyUtils.java | 2 +- .../secp256k1/filebased/CredentialSigner.java | 3 +- .../DefaultArtifactSignerProviderTest.java | 14 ++-- .../secp256k1/EthPublicKeyUtilsTest.java | 2 +- 12 files changed, 79 insertions(+), 54 deletions(-) diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java index 1ef6afba1..cc33ce011 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java @@ -60,7 +60,8 @@ static void initV3Keystores() throws IOException, GeneralSecurityException, Ciph publicKeys = new ArrayList<>(); for (int i = 0; i < 4; i++) { final ECKeyPair ecKeyPair = Keys.createEcKeyPair(); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(ecKeyPair.getPublicKey()); + final ECPublicKey ecPublicKey = + EthPublicKeyUtils.createPublicKeyFromBigInt(ecKeyPair.getPublicKey()); final String publicKeyHex = IdentifierUtils.normaliseIdentifier(EthPublicKeyUtils.toHexString(ecPublicKey)); publicKeys.add(publicKeyHex); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java index 297ad1b13..ba53f54ca 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java @@ -121,7 +121,7 @@ private void createConfigurationFiles(final Set opaqueDataIds, final Ke private String getPublicKey(final String key) { return normaliseIdentifier( EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.createPublicKey( + EthPublicKeyUtils.createPublicKeyFromBigInt( Credentials.create(key).getEcKeyPair().getPublicKey()))); } } 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 a4d5769c8..dacb73763 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 @@ -194,7 +194,8 @@ void verifySignature(final Bytes signature, final String publicKeyHex) { final byte[] s = signature.slice(32, 32).toArray(); final byte[] v = signature.slice(64).toArray(); final BigInteger messagePublicKey = recoverPublicKey(new SignatureData(v, r, s)); - assertThat(EthPublicKeyUtils.createPublicKey(messagePublicKey)).isEqualTo(expectedPublicKey); + assertThat(EthPublicKeyUtils.createPublicKeyFromBigInt(messagePublicKey)) + .isEqualTo(expectedPublicKey); } private BigInteger recoverPublicKey(final SignatureData signature) { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index b743149d6..9e8e377f3 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -62,7 +62,7 @@ public ArtifactSigner generateECProxyKey(final String identifier) final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); final Path ecWalletFile = createECWalletFile(ecKeyPair, identifier); LOG.debug("Created proxy EC wallet file {} for identifier: {}", ecWalletFile, identifier); - return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair))); + return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair)), true); } public ArtifactSigner generateBLSProxyKey(final String identifier) throws UncheckedIOException { diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java index e0aab9d00..06eda8bbe 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java @@ -108,7 +108,8 @@ public void ifAddressIsNotUnlockedExceptionIsThrownWithSigningNotUnlocked() { public void signatureHasTheExpectedFormat() { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); - final ECPublicKey key = EthPublicKeyUtils.createPublicKey(cs.getEcKeyPair().getPublicKey()); + final ECPublicKey key = + EthPublicKeyUtils.createPublicKeyFromBigInt(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); final BigInteger v = BigInteger.ONE; @@ -169,7 +170,8 @@ public void returnsExpectedSignatureForEip1559Transaction() { private String executeEthSignTransaction(final JsonObject params) { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); - final ECPublicKey key = EthPublicKeyUtils.createPublicKey(cs.getEcKeyPair().getPublicKey()); + final ECPublicKey key = + EthPublicKeyUtils.createPublicKeyFromBigInt(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); doAnswer( diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java index 7e572c5f5..51525861a 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java @@ -22,15 +22,23 @@ public class EthSecpArtifactSigner implements ArtifactSigner { private final Signer signer; + private final boolean isCompressed; public EthSecpArtifactSigner(final Signer signer) { this.signer = signer; + this.isCompressed = false; + } + + public EthSecpArtifactSigner(final Signer signer, final boolean isCompressed) { + this.signer = signer; + this.isCompressed = isCompressed; } @Override public String getIdentifier() { - return IdentifierUtils.normaliseIdentifier( - EthPublicKeyUtils.toHexString(signer.getPublicKey())); + final String hexString = + EthPublicKeyUtils.getEncoded(signer.getPublicKey(), isCompressed, false).toHexString(); + return IdentifierUtils.normaliseIdentifier(hexString); } @Override diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java index d9e998c55..6fd5f9950 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java @@ -36,6 +36,11 @@ public class SecpV3KeystoresBulkLoader { public static MappedResults loadV3KeystoresUsingPasswordFileOrDir( final Path keystoresPath, final Path pwrdFileOrDirPath) { + return loadV3KeystoresUsingPasswordFileOrDir(keystoresPath, pwrdFileOrDirPath, false); + } + + public static MappedResults loadV3KeystoresUsingPasswordFileOrDir( + final Path keystoresPath, final Path pwrdFileOrDirPath, final boolean isCompressed) { if (!Files.exists(pwrdFileOrDirPath)) { LOG.error("Password file or directory doesn't exist."); return MappedResults.errorResult(); @@ -67,12 +72,12 @@ public static MappedResults loadV3KeystoresUsingPasswordFileOrDi } return keystoresFiles.parallelStream() - .map(keystoreFile -> createSecpArtifactSigner(keystoreFile, passwordReader)) + .map(keystoreFile -> createSecpArtifactSigner(keystoreFile, passwordReader, isCompressed)) .reduce(MappedResults.newSetInstance(), MappedResults::merge); } private static MappedResults createSecpArtifactSigner( - final Path v3KeystorePath, final PasswordReader passwordReader) { + final Path v3KeystorePath, final PasswordReader passwordReader, final boolean isCompressed) { try { final String fileNameWithoutExt = FilenameUtils.removeExtension(v3KeystorePath.getFileName().toString()); @@ -82,7 +87,7 @@ private static MappedResults createSecpArtifactSigner( final Credentials credentials = WalletUtils.loadCredentials(password, v3KeystorePath.toFile()); final EthSecpArtifactSigner artifactSigner = - new EthSecpArtifactSigner(new CredentialSigner(credentials)); + new EthSecpArtifactSigner(new CredentialSigner(credentials), isCompressed); return MappedResults.newInstance(Set.of(artifactSigner), 0); } catch (final IOException | CipherException | RuntimeException e) { LOG.error("Error loading v3 keystore {}", v3KeystorePath, e); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 448942631..a36773c9d 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -77,41 +77,44 @@ public Future load() { if (!keystoreParameter.isEnabled()) { return; } - signers.forEach( - (identifier, __) -> { - final Path identifierPath = - keystoreParameter.getKeystoresPath().resolve(identifier); - if (identifierPath.toFile().canRead() - && identifierPath.toFile().isDirectory()) { - final Path v4Dir = identifierPath.resolve("v4"); - - if (v4Dir.toFile().canRead() && v4Dir.toFile().isDirectory()) { - // load v4 proxy signers - final BlsKeystoreBulkLoader v4Loader = new BlsKeystoreBulkLoader(); - final MappedResults blsSignersResult = - v4Loader.loadKeystoresUsingPasswordFile( - v4Dir, keystoreParameter.getKeystoresPasswordFile()); - final Collection blsSigners = - blsSignersResult.getValues(); - proxySigners - .computeIfAbsent(identifier, k -> new ArrayList<>()) - .addAll(blsSigners); - } - - final Path v3Dir = identifierPath.resolve("v3"); - if (v3Dir.toFile().canRead() && v3Dir.toFile().isDirectory()) { - // load v3 proxy signers - final MappedResults secpSignersResults = - SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( - v3Dir, keystoreParameter.getKeystoresPasswordFile()); - final Collection secpSigners = - secpSignersResults.getValues(); - proxySigners - .computeIfAbsent(identifier, k -> new ArrayList<>()) - .addAll(secpSigners); - } - } - }); + + signers + .keySet() + .forEach( + identifier -> { + final Path identifierPath = + keystoreParameter.getKeystoresPath().resolve(identifier); + if (identifierPath.toFile().canRead() + && identifierPath.toFile().isDirectory()) { + final Path v4Dir = identifierPath.resolve("v4"); + + if (v4Dir.toFile().canRead() && v4Dir.toFile().isDirectory()) { + // load v4 proxy signers + final BlsKeystoreBulkLoader v4Loader = new BlsKeystoreBulkLoader(); + final MappedResults blsSignersResult = + v4Loader.loadKeystoresUsingPasswordFile( + v4Dir, keystoreParameter.getKeystoresPasswordFile()); + final Collection blsSigners = + blsSignersResult.getValues(); + proxySigners + .computeIfAbsent(identifier, k -> new ArrayList<>()) + .addAll(blsSigners); + } + + final Path v3Dir = identifierPath.resolve("v3"); + if (v3Dir.toFile().canRead() && v3Dir.toFile().isDirectory()) { + // load v3 proxy signers (compressed pub key). + final MappedResults secpSignersResults = + SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( + v3Dir, keystoreParameter.getKeystoresPasswordFile(), true); + final Collection secpSigners = + secpSignersResults.getValues(); + proxySigners + .computeIfAbsent(identifier, k -> new ArrayList<>()) + .addAll(secpSigners); + } + } + }); }); LOG.info("Total signers (keys) currently loaded in memory: {}", signers.size()); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index fad49d559..6bbbc6fdf 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -128,7 +128,7 @@ private static ECPublicKey createPublicKeyFromPoint(final ECPoint point) { * @return The created ECPublicKey * @throws IllegalArgumentException if the input is invalid */ - public static ECPublicKey createPublicKey(final BigInteger publicKeyValue) { + public static ECPublicKey createPublicKeyFromBigInt(final BigInteger publicKeyValue) { if (publicKeyValue == null) { throw new IllegalArgumentException("Public key value cannot be null"); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java index 090f77747..9cf5d5670 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java @@ -31,7 +31,8 @@ public class CredentialSigner implements Signer { public CredentialSigner(final Credentials credentials, final boolean needToHash) { this.credentials = credentials; - this.publicKey = EthPublicKeyUtils.createPublicKey(credentials.getEcKeyPair().getPublicKey()); + this.publicKey = + EthPublicKeyUtils.createPublicKeyFromBigInt(credentials.getEcKeyPair().getPublicKey()); this.needToHash = needToHash; } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 3a697e830..5a2a0e4d6 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -143,7 +143,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { assertThat(key1ProxyPublicKeys.get(KeyType.BLS)) .containsExactlyInAnyOrder(getPublicKeysArray(key1ProxyKeyPairs)); assertThat(key1ProxyPublicKeys.get(KeyType.SECP256K1)) - .containsExactlyInAnyOrder(getSecpPublicKeysArray(key1SecpKeyPairs)); + .containsExactlyInAnyOrder(getCompressedSecpPublicKeysArray(key1SecpKeyPairs)); final Map> key2ProxyPublicKeys = signerProvider.getProxyIdentifiers(PUBLIC_KEY2); @@ -151,7 +151,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { assertThat(key2ProxyPublicKeys.get(KeyType.BLS)) .containsExactlyInAnyOrder(getPublicKeysArray(key2ProxyKeyPairs)); assertThat(key2ProxyPublicKeys.get(KeyType.SECP256K1)) - .containsExactlyInAnyOrder(getSecpPublicKeysArray(key2SecpKeyPairs)); + .containsExactlyInAnyOrder(getCompressedSecpPublicKeysArray(key2SecpKeyPairs)); } @Test @@ -223,12 +223,16 @@ private static String[] getPublicKeysArray(final List blsKeyPairs) { .toArray(String[]::new); } - private static String[] getSecpPublicKeysArray(final List ecKeyPairs) { + private static String[] getCompressedSecpPublicKeysArray(final List ecKeyPairs) { + // compressed public keys return ecKeyPairs.stream() .map( keyPair -> - EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.createPublicKey(keyPair.getPublicKey()))) + EthPublicKeyUtils.getEncoded( + EthPublicKeyUtils.createPublicKeyFromBigInt(keyPair.getPublicKey()), + true, + false) + .toHexString()) .toList() .toArray(String[]::new); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index e4889fe0a..db8a1ccaf 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -60,7 +60,7 @@ public void createsPublicKeyFromBytes() { @Test public void createsPublicKeyFromBigInteger() { final BigInteger publicKey = Numeric.toBigInt(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKey); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKeyFromBigInt(publicKey); final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); From 1f1894675f1b21bbc23aa32e32b951de2ac10511 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 21:10:24 +1000 Subject: [PATCH 14/57] Commit Boost - Compute signing root for ECPublicKey --- .../dsl/utils/Eth2RequestUtils.java | 6 +-- .../CommitBoostGenerateProxyKeyHandler.java | 27 +++++----- .../datastructure/SECPProxyKeyMessage.java | 38 ++++++++++++++ .../datastructure/SECPProxyKeySchema.java | 40 ++++++++++++++ .../datastructure/SszSECPPublicKey.java | 52 +++++++++++++++++++ .../datastructure/SszSECPPublicKeySchema.java | 39 ++++++++++++++ .../eth2/Eth2SignForIdentifierHandler.java | 6 +-- .../core/util/CommitBoostSigningRootUtil.java | 19 ------- ...il.java => Web3SignerSigningRootUtil.java} | 2 +- 9 files changed, 190 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKeySchema.java delete mode 100644 core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java rename core/src/main/java/tech/pegasys/web3signer/core/util/{DepositSigningRootUtil.java => Web3SignerSigningRootUtil.java} (97%) 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 95b9df5a1..c029bead2 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 @@ -13,7 +13,7 @@ package tech.pegasys.web3signer.dsl.utils; import static java.util.Collections.emptyList; -import static tech.pegasys.web3signer.core.util.DepositSigningRootUtil.computeDomain; +import static tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil.computeDomain; import tech.pegasys.teku.api.schema.AggregateAndProof; import tech.pegasys.teku.api.schema.Attestation; @@ -48,7 +48,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.util.DepositSigningRootUtil; +import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -216,7 +216,7 @@ private static Eth2SigningRequestBody createDepositRequest() { genesisForkVersion); final Bytes32 depositDomain = computeDomain(Domain.DEPOSIT, genesisForkVersion, Bytes32.ZERO); final Bytes signingRoot = - DepositSigningRootUtil.computeSigningRoot( + Web3SignerSigningRootUtil.computeSigningRoot( depositMessage.asInternalDepositMessage(), depositDomain); return Eth2SigningRequestBodyBuilder.anEth2SigningRequestBody() .withType(ArtifactType.DEPOSIT) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 489c8db60..e8a6069b7 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -18,20 +18,22 @@ import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.ssz.Merkleizable; import tech.pegasys.teku.spec.Spec; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeySchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.SECPProxyKeySchema; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; -import tech.pegasys.web3signer.core.util.CommitBoostSigningRootUtil; -import tech.pegasys.web3signer.core.util.DepositSigningRootUtil; +import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import java.security.interfaces.ECPublicKey; import java.util.Optional; import com.fasterxml.jackson.core.JsonProcessingException; @@ -49,6 +51,7 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler signerForIdentifier; private final ProxyKeyGenerator proxyKeyGenerator; @@ -130,21 +133,19 @@ private Bytes computeSigningRoot( final Bytes4 genesisForkVersion = eth2Spec.getGenesisSpec().getConfig().getGenesisForkVersion(); final Bytes32 domain = - DepositSigningRootUtil.computeDomain( - CommitBoostSigningRootUtil.COMMIT_BOOST_DOMAIN, - genesisForkVersion, - genesisValidatorsRoot); + Web3SignerSigningRootUtil.computeDomain( + COMMIT_BOOST_DOMAIN, genesisForkVersion, genesisValidatorsRoot); final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyKeyMessage.blsPublicKey()); + final Merkleizable proxyKeyMessageToSign; if (scheme == ProxyKeySignatureScheme.BLS) { final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyKeyMessage.proxyPublicKey()); - final BlsProxyKeyMessage blsProxyKeyMessage = - new BlsProxyKeySchema().create(delegator, proxy); - - return DepositSigningRootUtil.computeSigningRoot(blsProxyKeyMessage, domain); + proxyKeyMessageToSign = new BlsProxyKeySchema().create(delegator, proxy); } else { - // TODO: Implement ECDSA signing root - throw new UnsupportedOperationException("ECDSA signing root not implemented"); + final ECPublicKey proxy = + EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); + proxyKeyMessageToSign = new SECPProxyKeySchema().create(delegator, proxy); } + return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java new file mode 100644 index 000000000..8694a3878 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java @@ -0,0 +1,38 @@ +/* + * 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.datastructure; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; + +import java.security.interfaces.ECPublicKey; + +public class SECPProxyKeyMessage + extends Container2 { + + public SECPProxyKeyMessage( + final SECPProxyKeySchema schema, final BLSPublicKey delegator, final ECPublicKey proxy) { + super(schema, new SszPublicKey(delegator), new SszSECPPublicKey(proxy)); + } + + SECPProxyKeyMessage(final SECPProxyKeySchema schema, final TreeNode backingNode) { + super(schema, backingNode); + } + + @Override + public SECPProxyKeySchema getSchema() { + return (SECPProxyKeySchema) super.getSchema(); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java new file mode 100644 index 000000000..65dcc31f8 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java @@ -0,0 +1,40 @@ +/* + * 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.datastructure; + +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; +import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; + +import java.security.interfaces.ECPublicKey; + +public class SECPProxyKeySchema + extends ContainerSchema2 { + public SECPProxyKeySchema() { + super( + "SECPProxyKeyMessage", + namedSchema("delegator", SszPublicKeySchema.INSTANCE), + namedSchema("proxy", SszSECPPublicKeySchema.INSTANCE)); + } + + public SECPProxyKeyMessage create(final BLSPublicKey delegator, final ECPublicKey proxy) { + return new SECPProxyKeyMessage(this, delegator, proxy); + } + + @Override + public SECPProxyKeyMessage createFromBackingNode(final TreeNode treeNode) { + return new SECPProxyKeyMessage(this, treeNode); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java new file mode 100644 index 000000000..ec31be69c --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 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.datastructure; + +import tech.pegasys.teku.infrastructure.ssz.collections.impl.SszByteVectorImpl; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; + +import java.security.interfaces.ECPublicKey; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.tuweni.bytes.Bytes; + +public class SszSECPPublicKey extends SszByteVectorImpl { + + private final Supplier publicKey; + + public SszSECPPublicKey(final Bytes publicKeyBytes) { + super(SszSECPPublicKeySchema.INSTANCE, publicKeyBytes); + this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.createPublicKey(publicKeyBytes)); + } + + public SszSECPPublicKey(final ECPublicKey publicKey) { + super(SszSECPPublicKeySchema.INSTANCE, EthPublicKeyUtils.getEncoded(publicKey, true, true)); + this.publicKey = () -> publicKey; + } + + SszSECPPublicKey(final TreeNode backingNode) { + super(SszSECPPublicKeySchema.INSTANCE, backingNode); + this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.createPublicKey(getBytes())); + } + + public ECPublicKey getECPublicKey() { + return publicKey.get(); + } + + @Override + public SszSECPPublicKeySchema getSchema() { + return (SszSECPPublicKeySchema) super.getSchema(); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKeySchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKeySchema.java new file mode 100644 index 000000000..2aa30f34c --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKeySchema.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 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.datastructure; + +import tech.pegasys.teku.infrastructure.json.types.DeserializableTypeDefinition; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.impl.SszByteVectorSchemaImpl; +import tech.pegasys.teku.infrastructure.ssz.schema.json.SszPrimitiveTypeDefinitions; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; + +public class SszSECPPublicKeySchema extends SszByteVectorSchemaImpl { + private static final int SECP_COMPRESSED_PUBLIC_KEY_SIZE = 33; + + public static final SszSECPPublicKeySchema INSTANCE = new SszSECPPublicKeySchema(); + + private SszSECPPublicKeySchema() { + super(SszPrimitiveSchemas.BYTE_SCHEMA, SECP_COMPRESSED_PUBLIC_KEY_SIZE); + } + + @Override + protected DeserializableTypeDefinition createTypeDefinition() { + return SszPrimitiveTypeDefinitions.sszSerializedType(this, "Bytes33 hexadecimal"); + } + + @Override + public SszSECPPublicKey createFromBackingNode(final TreeNode node) { + return new SszSECPPublicKey(node); + } +} 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 ffca43dd7..3e8a7a974 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 @@ -16,7 +16,7 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE; import static tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.JSON_UTF_8; import static tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.TEXT_PLAIN_UTF_8; -import static tech.pegasys.web3signer.core.util.DepositSigningRootUtil.computeDomain; +import static tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil.computeDomain; import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier; import tech.pegasys.teku.api.schema.AttestationData; @@ -31,7 +31,7 @@ import tech.pegasys.web3signer.core.service.http.ArtifactType; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics; -import tech.pegasys.web3signer.core.util.DepositSigningRootUtil; +import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import tech.pegasys.web3signer.slashingprotection.SlashingProtection; import java.util.List; @@ -281,7 +281,7 @@ private Bytes computeSigningRoot(final Eth2SigningRequestBody body) { checkArgument(body.deposit() != null, "deposit must be specified"); final Bytes32 depositDomain = computeDomain(Domain.DEPOSIT, body.deposit().getGenesisForkVersion(), Bytes32.ZERO); - return DepositSigningRootUtil.computeSigningRoot( + return Web3SignerSigningRootUtil.computeSigningRoot( body.deposit().asInternalDepositMessage(), depositDomain); case SYNC_COMMITTEE_MESSAGE: final SyncCommitteeMessage syncCommitteeMessage = body.syncCommitteeMessage(); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java b/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java deleted file mode 100644 index 66452c97f..000000000 --- a/core/src/main/java/tech/pegasys/web3signer/core/util/CommitBoostSigningRootUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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.util; - -import tech.pegasys.teku.infrastructure.bytes.Bytes4; - -public class CommitBoostSigningRootUtil { - public static final Bytes4 COMMIT_BOOST_DOMAIN = Bytes4.fromHexString("0x6d6d6f43"); -} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/util/DepositSigningRootUtil.java b/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java similarity index 97% rename from core/src/main/java/tech/pegasys/web3signer/core/util/DepositSigningRootUtil.java rename to core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java index 103fed68a..39e1b536b 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/util/DepositSigningRootUtil.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java @@ -20,7 +20,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -public class DepositSigningRootUtil { +public class Web3SignerSigningRootUtil { public static Bytes computeSigningRoot(final Merkleizable object, final Bytes32 domain) { return new SigningData(object.hashTreeRoot(), domain).hashTreeRoot(); } From b261b8404b610269f4cbfa1ef9bceec40265d59d Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 21:21:33 +1000 Subject: [PATCH 15/57] Seperate out SigningRootGenerator for Commit Boost --- .../CommitBoostGenerateProxyKeyHandler.java | 41 ++----------- .../commitboost/SigningRootGenerator.java | 57 +++++++++++++++++++ 2 files changed, 61 insertions(+), 37 deletions(-) create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index e8a6069b7..20d49d881 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -16,24 +16,15 @@ 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.bls.BLSPublicKey; -import tech.pegasys.teku.infrastructure.bytes.Bytes4; -import tech.pegasys.teku.infrastructure.ssz.Merkleizable; import tech.pegasys.teku.spec.Spec; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeySchema; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.SECPProxyKeySchema; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; -import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.config.KeystoresParameters; -import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; -import java.security.interfaces.ECPublicKey; import java.util.Optional; import com.fasterxml.jackson.core.JsonProcessingException; @@ -42,7 +33,6 @@ import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { @@ -51,12 +41,10 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler signerForIdentifier; private final ProxyKeyGenerator proxyKeyGenerator; - private final Spec eth2Spec; - private final Bytes32 genesisValidatorsRoot; + private final SigningRootGenerator signingRootGenerator; public CommitBoostGenerateProxyKeyHandler( final SignerForIdentifier signerForIdentifier, @@ -65,8 +53,7 @@ public CommitBoostGenerateProxyKeyHandler( final Bytes32 genesisValidatorsRoot) { this.signerForIdentifier = signerForIdentifier; this.proxyKeyGenerator = new ProxyKeyGenerator(commitBoostApiParameters); - this.eth2Spec = eth2Spec; - this.genesisValidatorsRoot = genesisValidatorsRoot; + this.signingRootGenerator = new SigningRootGenerator(eth2Spec, genesisValidatorsRoot); } @Override @@ -108,7 +95,8 @@ public void handle(final RoutingContext context) { new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); final Optional optionalSig = signerForIdentifier.sign( - identifier, computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme())); + identifier, + signingRootGenerator.computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme())); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); return; @@ -127,25 +115,4 @@ public void handle(final RoutingContext context) { context.fail(INTERNAL_ERROR); } } - - private Bytes computeSigningRoot( - final ProxyKeyMessage proxyKeyMessage, final ProxyKeySignatureScheme scheme) { - final Bytes4 genesisForkVersion = eth2Spec.getGenesisSpec().getConfig().getGenesisForkVersion(); - - final Bytes32 domain = - Web3SignerSigningRootUtil.computeDomain( - COMMIT_BOOST_DOMAIN, genesisForkVersion, genesisValidatorsRoot); - - final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyKeyMessage.blsPublicKey()); - final Merkleizable proxyKeyMessageToSign; - if (scheme == ProxyKeySignatureScheme.BLS) { - final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyKeyMessage.proxyPublicKey()); - proxyKeyMessageToSign = new BlsProxyKeySchema().create(delegator, proxy); - } else { - final ECPublicKey proxy = - EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); - proxyKeyMessageToSign = new SECPProxyKeySchema().create(delegator, proxy); - } - return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); - } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java new file mode 100644 index 000000000..38cb4a3ad --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -0,0 +1,57 @@ +/* + * 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 tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.ssz.Merkleizable; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeySchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.SECPProxyKeySchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; +import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; + +import java.security.interfaces.ECPublicKey; + +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; + +public class SigningRootGenerator { + private static final Bytes4 COMMIT_BOOST_DOMAIN = Bytes4.fromHexString("0x6d6d6f43"); + private final Bytes32 domain; + + public SigningRootGenerator(final Spec eth2Spec, final Bytes32 genesisValidatorsRoot) { + final Bytes4 genesisForkVersion = eth2Spec.getGenesisSpec().getConfig().getGenesisForkVersion(); + domain = + Web3SignerSigningRootUtil.computeDomain( + COMMIT_BOOST_DOMAIN, genesisForkVersion, genesisValidatorsRoot); + } + + public Bytes computeSigningRoot( + final ProxyKeyMessage proxyKeyMessage, final ProxyKeySignatureScheme scheme) { + + final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyKeyMessage.blsPublicKey()); + final Merkleizable proxyKeyMessageToSign; + if (scheme == ProxyKeySignatureScheme.BLS) { + final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyKeyMessage.proxyPublicKey()); + proxyKeyMessageToSign = new BlsProxyKeySchema().create(delegator, proxy); + } else { + final ECPublicKey proxy = + EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); + proxyKeyMessageToSign = new SECPProxyKeySchema().create(delegator, proxy); + } + return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); + } +} From a2b0c15ff5ee15375bdcaa61c11eb041284bae01 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 23 Oct 2024 21:38:44 +1000 Subject: [PATCH 16/57] Fix EthAccountsResultProviderTest to use proper ECPublicKeys --- .../EthAccountsResultProviderTest.java | 62 +++++++++---------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java index 6923303dd..ba761cf6a 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthAccountsResultProviderTest.java @@ -22,30 +22,37 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse.EthAccountsResultProvider; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import com.google.common.collect.Sets; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import org.web3j.crypto.Keys; -@SuppressWarnings("unchecked") public class EthAccountsResultProviderTest { - - final ECPublicKey publicKeyA = createKeyFrom("A".repeat(128)); - final ECPublicKey publicKeyB = createKeyFrom("B".repeat(128)); - final ECPublicKey publicKeyC = createKeyFrom("C".repeat(128)); - - final String addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); - final String addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); - final String addressC = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyC)); - - final ECPublicKey createKeyFrom(final String hexString) { - return EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(hexString)); + private final ECPublicKey publicKeyA; + private final ECPublicKey publicKeyB; + private final ECPublicKey publicKeyC; + + private final String addressA; + private final String addressB; + private final String addressC; + + public EthAccountsResultProviderTest() throws GeneralSecurityException { + SecureRandom secureRandom = new SecureRandom(); + publicKeyA = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + publicKeyB = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + publicKeyC = (ECPublicKey) EthPublicKeyUtils.createSecp256k1KeyPair(secureRandom).getPublic(); + + addressA = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyA)); + addressB = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyB)); + addressC = Keys.getAddress(EthPublicKeyUtils.toHexString(publicKeyC)); } @Test @@ -103,9 +110,7 @@ public void missingParametersIsOk() { final JsonRpcRequest request = new JsonRpcRequest("2.0", "eth_accounts"); request.setId(new JsonRpcRequestId(id)); - final Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - final List addressses = (List) body; + final List addressses = resultProvider.createResponseResult(request); assertThat(addressses).containsExactly("0x" + addressA); } @@ -120,10 +125,7 @@ public void multipleValueFromBodyProviderInsertedToResult() { request.setId(new JsonRpcRequestId(id)); request.setParams(emptyList()); - final Object body = resultProvider.createResponseResult(request); - - assertThat(body).isInstanceOf(List.class); - final List reportedAddresses = (List) body; + final List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyInAnyOrder("0x" + addressA, "0x" + addressB, "0x" + addressC); } @@ -139,25 +141,19 @@ public void accountsReturnedAreDynamicallyFetchedFromProvider() { request.setId(new JsonRpcRequestId(1)); request.setParams(emptyList()); - Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - List reportedAddresses = (List) body; + List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressA, "0x" + addressB, "0x" + addressC).stream() + Stream.of("0x" + addressA, "0x" + addressB, "0x" + addressC) .sorted() .collect(Collectors.toList())); addresses.remove(publicKeyA); - body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - reportedAddresses = (List) body; + reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressB, "0x" + addressC).stream() - .sorted() - .collect(Collectors.toList())); + Stream.of("0x" + addressB, "0x" + addressC).sorted().collect(Collectors.toList())); } @Test @@ -169,12 +165,10 @@ public void accountsReturnedAreSortedAlphabetically() { request.setId(new JsonRpcRequestId(1)); request.setParams(emptyList()); - final Object body = resultProvider.createResponseResult(request); - assertThat(body).isInstanceOf(List.class); - List reportedAddresses = (List) body; + List reportedAddresses = resultProvider.createResponseResult(request); assertThat(reportedAddresses) .containsExactlyElementsOf( - List.of("0x" + addressA, "0x" + addressB, "0x" + addressC).stream() + Stream.of("0x" + addressA, "0x" + addressB, "0x" + addressC) .sorted() .collect(Collectors.toList())); } From afc10d90c4a4b0410a8d6e3d6a27a9f2e0a4a741 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 09:36:07 +1000 Subject: [PATCH 17/57] Fix Eth1AddressSignerIdentifierTest to use proper SECP keys --- .../Eth1AddressSignerIdentifierTest.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java index b33695eb4..1c929b644 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java @@ -15,53 +15,88 @@ import static org.assertj.core.api.Assertions.assertThat; import tech.pegasys.web3signer.core.Eth1AddressSignerIdentifier; -import tech.pegasys.web3signer.core.util.PublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.SignerIdentifier; import tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil; +import java.security.KeyPair; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.util.Locale; +import java.util.Random; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.web3j.crypto.Keys; class Eth1AddressSignerIdentifierTest { + private static KeyPair secp256k1KeyPair; + private static KeyPair secp256k1KeyPair2; + + @BeforeAll + static void generateKeyPair() throws Exception { + final SecureRandom random = new SecureRandom(); + secp256k1KeyPair = EthPublicKeyUtils.createSecp256k1KeyPair(random); + secp256k1KeyPair2 = EthPublicKeyUtils.createSecp256k1KeyPair(random); + } @Test void prefixIsRemovedFromAddress() { - final Eth1AddressSignerIdentifier signerIdentifier = new Eth1AddressSignerIdentifier("0xAb"); - assertThat(signerIdentifier.toStringIdentifier()).isEqualTo("ab"); + final String address = + Keys.getAddress( + EthPublicKeyUtils.ecPublicKeyToBigInteger((ECPublicKey) secp256k1KeyPair.getPublic())); + // forcefully convert some random alphabets to uppercase + final String mixCaseAddress = convertRandomAlphabetsToUpperCase(address); + + final Eth1AddressSignerIdentifier signerIdentifier = + new Eth1AddressSignerIdentifier(mixCaseAddress); + assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(address.toLowerCase(Locale.US)); } @Test void validateWorksForSamePrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); assertThat(signerIdentifier.validate(publicKey)).isTrue(); } @Test void validateFailsForDifferentPrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); - assertThat(signerIdentifier.validate(PublicKeyUtils.createKeyFrom("0xbb"))).isFalse(); + assertThat(signerIdentifier.validate((ECPublicKey) secp256k1KeyPair2.getPublic())).isFalse(); } @Test void validateFailsForNullPrimaryKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); assertThat(signerIdentifier.validate(null)).isFalse(); } @Test void correctEth1AddressIsGeneratedFromPublicKey() { - final ECPublicKey publicKey = PublicKeyUtils.createKeyFrom("0xab"); + final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); final String prefixRemovedAddress = AddressUtil.remove0xPrefix( Keys.getAddress(EthPublicKeyUtils.toHexString(publicKey)).toLowerCase(Locale.US)); assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(prefixRemovedAddress); } + + private static String convertRandomAlphabetsToUpperCase(final String input) { + final char[] chars = input.toCharArray(); + final Random random = new Random(); + int count = 0; + + while (count < 2 || count < 3 && random.nextBoolean()) { + int index = random.nextInt(chars.length); + if (Character.isLetter(chars[index]) && Character.isLowerCase(chars[index])) { + chars[index] = Character.toUpperCase(chars[index]); + count++; + } + } + + return new String(chars); + } } From bfb35d335b1fda9dcd3838ac7b38d64864fe321d Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 10:02:46 +1000 Subject: [PATCH 18/57] Fix Eth1AddressSignerIdentifier and unit test --- .../core/Eth1AddressSignerIdentifier.java | 7 ++-- .../Eth1AddressSignerIdentifierTest.java | 33 ++++++++++--------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java index 4a1ad0892..14f711301 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth1AddressSignerIdentifier.java @@ -13,6 +13,7 @@ package tech.pegasys.web3signer.core; import static org.web3j.crypto.Keys.getAddress; +import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.ecPublicKeyToBigInteger; import static tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils.toHexString; import static tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil.remove0xPrefix; @@ -31,11 +32,7 @@ public Eth1AddressSignerIdentifier(final String address) { } public static SignerIdentifier fromPublicKey(final ECPublicKey publicKey) { - return new Eth1AddressSignerIdentifier(getAddress(toHexString(publicKey))); - } - - public static SignerIdentifier fromPublicKey(final String publicKey) { - return new Eth1AddressSignerIdentifier(getAddress(publicKey)); + return new Eth1AddressSignerIdentifier(getAddress(ecPublicKeyToBigInteger(publicKey))); } @Override diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java index 1c929b644..cc7b5e687 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/Eth1AddressSignerIdentifierTest.java @@ -17,13 +17,10 @@ import tech.pegasys.web3signer.core.Eth1AddressSignerIdentifier; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.SignerIdentifier; -import tech.pegasys.web3signer.signing.secp256k1.util.AddressUtil; import java.security.KeyPair; import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; -import java.util.Locale; -import java.util.Random; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -42,15 +39,18 @@ static void generateKeyPair() throws Exception { @Test void prefixIsRemovedFromAddress() { + // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix final String address = Keys.getAddress( EthPublicKeyUtils.ecPublicKeyToBigInteger((ECPublicKey) secp256k1KeyPair.getPublic())); - // forcefully convert some random alphabets to uppercase - final String mixCaseAddress = convertRandomAlphabetsToUpperCase(address); + // forcefully convert first two alphabets to uppercase and add prefix + final String mixCaseAddress = "0X" + convertAlphabetsToUpperCase(address); final Eth1AddressSignerIdentifier signerIdentifier = new Eth1AddressSignerIdentifier(mixCaseAddress); - assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(address.toLowerCase(Locale.US)); + assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(address); + assertThat(signerIdentifier.toStringIdentifier()).doesNotStartWithIgnoringCase("0x"); + assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); } @Test @@ -78,21 +78,22 @@ void validateFailsForNullPrimaryKey() { void correctEth1AddressIsGeneratedFromPublicKey() { final ECPublicKey publicKey = (ECPublicKey) secp256k1KeyPair.getPublic(); final SignerIdentifier signerIdentifier = Eth1AddressSignerIdentifier.fromPublicKey(publicKey); - final String prefixRemovedAddress = - AddressUtil.remove0xPrefix( - Keys.getAddress(EthPublicKeyUtils.toHexString(publicKey)).toLowerCase(Locale.US)); - assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(prefixRemovedAddress); + + // web3j.crypto.Keys.getAddress() returns lower case address without 0x prefix + final String expectedAddress = + Keys.getAddress(EthPublicKeyUtils.ecPublicKeyToBigInteger(publicKey)); + assertThat(signerIdentifier.toStringIdentifier()).isEqualTo(expectedAddress); + assertThat(signerIdentifier.toStringIdentifier()).doesNotStartWithIgnoringCase("0x"); + assertThat(signerIdentifier.toStringIdentifier()).isLowerCase(); } - private static String convertRandomAlphabetsToUpperCase(final String input) { + public static String convertAlphabetsToUpperCase(final String input) { final char[] chars = input.toCharArray(); - final Random random = new Random(); int count = 0; - while (count < 2 || count < 3 && random.nextBoolean()) { - int index = random.nextInt(chars.length); - if (Character.isLetter(chars[index]) && Character.isLowerCase(chars[index])) { - chars[index] = Character.toUpperCase(chars[index]); + for (int i = 0; i < chars.length && count < 2; i++) { + if (Character.isLetter(chars[i]) && Character.isLowerCase(chars[i])) { + chars[i] = Character.toUpperCase(chars[i]); count++; } } From 501e3d88be203be8bf189e161cef5507ef075a6c Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 13:33:08 +1000 Subject: [PATCH 19/57] prepping for unit test --- .../http/handlers/commitboost/SigningRootGenerator.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 38cb4a3ad..56e018a0e 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -12,6 +12,7 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.commitboost; +import com.google.common.annotations.VisibleForTesting; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.ssz.Merkleizable; @@ -54,4 +55,9 @@ public Bytes computeSigningRoot( } return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); } + + @VisibleForTesting + Bytes32 getDomain() { + return domain; + } } From a740c689d796ae90b3b99a68588cf8493857be64 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 13:54:57 +1000 Subject: [PATCH 20/57] refactoring DefaultArtifactSignerProvider and unit test --- .../config/DefaultArtifactSignerProvider.java | 101 +++++++++++------- .../DefaultArtifactSignerProviderTest.java | 8 +- 2 files changed, 65 insertions(+), 44 deletions(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 5d0e6f956..788cbc7b4 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -12,6 +12,9 @@ */ package tech.pegasys.web3signer.signing.config; +import static tech.pegasys.web3signer.signing.KeyType.BLS; +import static tech.pegasys.web3signer.signing.KeyType.SECP256K1; + import tech.pegasys.web3signer.keystorage.common.MappedResults; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; @@ -19,6 +22,7 @@ import tech.pegasys.web3signer.signing.bulkloading.BlsKeystoreBulkLoader; import tech.pegasys.web3signer.signing.bulkloading.SecpV3KeystoresBulkLoader; +import java.io.File; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -72,47 +76,25 @@ public Future load() { .forEach(signers::putIfAbsent); // for each loaded signer, load commit boost proxy signers (if any) - commitBoostKeystoresParameters.ifPresent( - keystoreParameter -> { - if (!keystoreParameter.isEnabled()) { - return; - } - signers.forEach( - (identifier, __) -> { - final Path identifierPath = - keystoreParameter.getKeystoresPath().resolve(identifier); - if (identifierPath.toFile().canRead() - && identifierPath.toFile().isDirectory()) { - final Path v4Dir = identifierPath.resolve("v4"); - - if (v4Dir.toFile().canRead() && v4Dir.toFile().isDirectory()) { - // load v4 proxy signers - final BlsKeystoreBulkLoader v4Loader = new BlsKeystoreBulkLoader(); - final MappedResults blsSignersResult = - v4Loader.loadKeystoresUsingPasswordFile( - v4Dir, keystoreParameter.getKeystoresPasswordFile()); - final Collection blsSigners = - blsSignersResult.getValues(); - proxySigners - .computeIfAbsent(identifier, k -> new ArrayList<>()) - .addAll(blsSigners); - } - - final Path v3Dir = identifierPath.resolve("v3"); - if (v3Dir.toFile().canRead() && v3Dir.toFile().isDirectory()) { - // load v3 proxy signers - final MappedResults secpSignersResults = - SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( - v3Dir, keystoreParameter.getKeystoresPasswordFile()); - final Collection secpSigners = - secpSignersResults.getValues(); - proxySigners - .computeIfAbsent(identifier, k -> new ArrayList<>()) - .addAll(secpSigners); - } - } - }); - }); + commitBoostKeystoresParameters + .filter(KeystoresParameters::isEnabled) + .ifPresent( + keystoreParameter -> + signers + .keySet() + .forEach( + signerIdentifier -> { + LOG.trace( + "Loading proxy signers for signer '{}' ...", signerIdentifier); + final Path identifierPath = + keystoreParameter.getKeystoresPath().resolve(signerIdentifier); + if (canReadFromDirectory(identifierPath)) { + loadBlsProxySigners( + keystoreParameter, signerIdentifier, identifierPath); + loadSecpProxySigners( + keystoreParameter, signerIdentifier, identifierPath); + } + })); LOG.info("Total signers (keys) currently loaded in memory: {}", signers.size()); return null; @@ -169,4 +151,41 @@ public Future removeSigner(final String identifier) { public void close() { executorService.shutdownNow(); } + + private static boolean canReadFromDirectory(final Path path) { + final File file = path.toFile(); + return file.canRead() && file.isDirectory(); + } + + private void loadSecpProxySigners( + final KeystoresParameters keystoreParameter, + final String identifier, + final Path identifierPath) { + final Path proxySecpDir = identifierPath.resolve(SECP256K1.name()); + if (canReadFromDirectory(proxySecpDir)) { + // load secp proxy signers + final MappedResults secpSignersResults = + SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( + proxySecpDir, keystoreParameter.getKeystoresPasswordFile()); + final Collection secpSigners = secpSignersResults.getValues(); + proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(secpSigners); + } + } + + private void loadBlsProxySigners( + final KeystoresParameters keystoreParameter, + final String identifier, + final Path identifierPath) { + final Path proxyBlsDir = identifierPath.resolve(BLS.name()); + + if (canReadFromDirectory(proxyBlsDir)) { + // load bls proxy signers + final BlsKeystoreBulkLoader blsKeystoreBulkLoader = new BlsKeystoreBulkLoader(); + final MappedResults blsSignersResult = + blsKeystoreBulkLoader.loadKeystoresUsingPasswordFile( + proxyBlsDir, keystoreParameter.getKeystoresPasswordFile()); + final Collection blsSigners = blsSignersResult.getValues(); + proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(blsSigners); + } + } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 2c8043f20..c28ced922 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -156,7 +156,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { @Test void emptyProxySignersAreLoadedSuccessfully() { - // enable commit boose without existing proxy keys + // enable commit boost without existing proxy keys final KeystoresParameters commitBoostParameters = new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); @@ -183,7 +183,8 @@ void emptyProxySignersAreLoadedSuccessfully() { private List randomBLSV4Keystores(SecureRandom secureRandom, String identifier) throws IOException { final Path v4Dir = - Files.createDirectories(commitBoostKeystoresPath.resolve(identifier).resolve("v4")); + Files.createDirectories( + commitBoostKeystoresPath.resolve(identifier).resolve(KeyType.BLS.name())); return IntStream.range(0, 4) .mapToObj( @@ -198,7 +199,8 @@ private List randomBLSV4Keystores(SecureRandom secureRandom, String private List randomSecpV3Keystores( final SecureRandom secureRandom, final String identifier) throws IOException { final Path v3Dir = - Files.createDirectories(commitBoostKeystoresPath.resolve(identifier).resolve("v3")); + Files.createDirectories( + commitBoostKeystoresPath.resolve(identifier).resolve(KeyType.SECP256K1.name())); return IntStream.range(0, 4) .mapToObj( i -> { From 9de3c0204decc1d9fb018d2d55be87419f9ec70e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 13:56:47 +1000 Subject: [PATCH 21/57] fix json encoding in publickeysroot for 500 --- .../core/routes/eth2/CommitBoostPublicKeysRoute.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java index fac7062de..261290049 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java @@ -44,7 +44,11 @@ public void register() { if (statusCode == 500) { ctx.response() .setStatusCode(statusCode) - .end(new JsonObject().put("error", "Internal Server Error").encode()); + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Internal Server Error") + .encode()); } else { ctx.next(); // go to global failure handler } From ac5017890820a8aaf98a8d8ba7e59b159a047338 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 14:09:34 +1000 Subject: [PATCH 22/57] cleaning up ProxyKeyGenerator to use BLS and SECP256k1 and subdirectories names --- .../commitboost/ProxyKeyGenerator.java | 35 ++++++++----------- .../commitboost/SigningRootGenerator.java | 2 +- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index 9e8e377f3..8a90c3bca 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -25,6 +25,7 @@ import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.BlsArtifactSigner; import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; +import tech.pegasys.web3signer.signing.KeyType; import tech.pegasys.web3signer.signing.config.KeystoresParameters; import tech.pegasys.web3signer.signing.config.metadata.SignerOrigin; import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; @@ -85,7 +86,8 @@ private Path createBLSKeystoreFile(final BLSKeyPair keyPair, final String identi keyPair.getSecretKey().toBytes(), publicKey, password, "", kdfParam, cipher); try { final Path keystoreDir = - createV4Directory(commitBoostApiParameters.getKeystoresPath(), identifier); + createSubDirectories( + commitBoostApiParameters.getKeystoresPath(), identifier, KeyType.BLS); final Path keystoreFile = keystoreDir.resolve(publicKey + ".json"); KeyStoreLoader.saveToFile(keystoreFile, keyStoreData); return keystoreFile; @@ -96,14 +98,16 @@ private Path createBLSKeystoreFile(final BLSKeyPair keyPair, final String identi private Path createECWalletFile(final ECKeyPair ecKeyPair, final String identifier) { final String password = readFile(commitBoostApiParameters.getKeystoresPasswordFile()); - final Path v3Dir = createV3Directory(commitBoostApiParameters.getKeystoresPath(), identifier); + final Path keystoreDir = + createSubDirectories( + commitBoostApiParameters.getKeystoresPath(), identifier, KeyType.SECP256K1); final String fileName; try { - fileName = WalletUtils.generateWalletFile(password, ecKeyPair, v3Dir.toFile(), true); - } catch (CipherException | IOException e) { + fileName = WalletUtils.generateWalletFile(password, ecKeyPair, keystoreDir.toFile(), true); + } catch (final CipherException | IOException e) { throw new RuntimeException(e); } - return v3Dir.resolve(fileName); + return keystoreDir.resolve(fileName); } private static String readFile(final Path file) throws UncheckedIOException { @@ -116,23 +120,14 @@ private static String readFile(final Path file) throws UncheckedIOException { return password; } - private static Path createV4Directory(final Path parentDirectory, final String directoryName) { - final Path v4Directory = parentDirectory.resolve(directoryName).resolve("v4"); + private static Path createSubDirectories( + final Path parentDirectory, final String directoryName, final KeyType keyType) { + final Path subDirectory = parentDirectory.resolve(directoryName).resolve(keyType.name()); try { - Files.createDirectories(v4Directory); + Files.createDirectories(subDirectory); } catch (final IOException e) { - throw new UncheckedIOException("Unable to create directory: " + v4Directory, e); + throw new UncheckedIOException("Unable to create directory: " + subDirectory, e); } - return v4Directory; - } - - private static Path createV3Directory(final Path parentDirectory, final String directoryName) { - final Path v3Directory = parentDirectory.resolve(directoryName).resolve("v3"); - try { - Files.createDirectories(v3Directory); - } catch (final IOException e) { - throw new UncheckedIOException("Unable to create directory: " + v3Directory, e); - } - return v3Directory; + return subDirectory; } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 56e018a0e..9f21f7cdf 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -12,7 +12,6 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.commitboost; -import com.google.common.annotations.VisibleForTesting; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.ssz.Merkleizable; @@ -26,6 +25,7 @@ import java.security.interfaces.ECPublicKey; +import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; From eed0048a4bb303c8131899341aa7fc755a55b04e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 14:31:55 +1000 Subject: [PATCH 23/57] simplify bulk loading for proxy keys --- .../pegasys/web3signer/core/Eth2Runner.java | 5 +- .../bulkloading/BlsKeystoreBulkLoader.java | 6 +- .../SecpV3KeystoresBulkLoader.java | 7 ++- .../config/DefaultArtifactSignerProvider.java | 61 ++++++++----------- 4 files changed, 37 insertions(+), 42 deletions(-) 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 400c3eb7d..5bc367802 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -242,13 +242,12 @@ private MappedResults bulkLoadSigners( if (keystoresParameters.isEnabled()) { LOG.info("Bulk loading keys from local keystores ... "); - final BlsKeystoreBulkLoader blsKeystoreBulkLoader = new BlsKeystoreBulkLoader(); final MappedResults keystoreSignersResult = keystoresParameters.hasKeystoresPasswordsPath() - ? blsKeystoreBulkLoader.loadKeystoresUsingPasswordDir( + ? BlsKeystoreBulkLoader.loadKeystoresUsingPasswordDir( keystoresParameters.getKeystoresPath(), keystoresParameters.getKeystoresPasswordsPath()) - : blsKeystoreBulkLoader.loadKeystoresUsingPasswordFile( + : BlsKeystoreBulkLoader.loadKeystoresUsingPasswordFile( keystoresParameters.getKeystoresPath(), keystoresParameters.getKeystoresPasswordFile()); LOG.info( diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/BlsKeystoreBulkLoader.java b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/BlsKeystoreBulkLoader.java index 93c66e2f8..53e3267ef 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/BlsKeystoreBulkLoader.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/BlsKeystoreBulkLoader.java @@ -39,7 +39,7 @@ public class BlsKeystoreBulkLoader { private static final Logger LOG = LogManager.getLogger(); - public MappedResults loadKeystoresUsingPasswordDir( + public static MappedResults loadKeystoresUsingPasswordDir( final Path keystoresDirectory, final Path passwordsDirectory) { final List keystoreFiles; try { @@ -59,7 +59,7 @@ public MappedResults loadKeystoresUsingPasswordDir( .reduce(MappedResults.newSetInstance(), MappedResults::merge); } - public MappedResults loadKeystoresUsingPasswordFile( + public static MappedResults loadKeystoresUsingPasswordFile( final Path keystoresDirectory, final Path passwordFile) { final List keystoreFiles; try { @@ -82,7 +82,7 @@ public MappedResults loadKeystoresUsingPasswordFile( .reduce(MappedResults.newSetInstance(), MappedResults::merge); } - private MappedResults createSignerForKeystore( + private static MappedResults createSignerForKeystore( final Path keystoreFile, final PasswordReader passwordReader) { try { LOG.debug("Loading keystore {}", keystoreFile); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java index 6fd5f9950..a63fcc8b9 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java @@ -39,7 +39,12 @@ public static MappedResults loadV3KeystoresUsingPasswordFileOrDi return loadV3KeystoresUsingPasswordFileOrDir(keystoresPath, pwrdFileOrDirPath, false); } - public static MappedResults loadV3KeystoresUsingPasswordFileOrDir( + public static MappedResults loadKeystoresWithCompressedIdentifier( + final Path keystoresPath, final Path pwrdFileOrDirPath) { + return loadV3KeystoresUsingPasswordFileOrDir(keystoresPath, pwrdFileOrDirPath, true); + } + + private static MappedResults loadV3KeystoresUsingPasswordFileOrDir( final Path keystoresPath, final Path pwrdFileOrDirPath, final boolean isCompressed) { if (!Files.exists(pwrdFileOrDirPath)) { LOG.error("Password file or directory doesn't exist."); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index ac5b7db46..534ca6709 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -34,6 +34,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -86,14 +87,18 @@ public Future load() { signerIdentifier -> { LOG.trace( "Loading proxy signers for signer '{}' ...", signerIdentifier); - final Path identifierPath = - keystoreParameter.getKeystoresPath().resolve(signerIdentifier); - if (canReadFromDirectory(identifierPath)) { - loadBlsProxySigners( - keystoreParameter, signerIdentifier, identifierPath); - loadSecpProxySigners( - keystoreParameter, signerIdentifier, identifierPath); - } + loadProxySigners( + keystoreParameter, + signerIdentifier, + SECP256K1.name(), + SecpV3KeystoresBulkLoader + ::loadKeystoresWithCompressedIdentifier); + + loadProxySigners( + keystoreParameter, + signerIdentifier, + BLS.name(), + BlsKeystoreBulkLoader::loadKeystoresUsingPasswordFile); })); LOG.info("Total signers (keys) currently loaded in memory: {}", signers.size()); @@ -169,35 +174,21 @@ private static boolean canReadFromDirectory(final Path path) { return file.canRead() && file.isDirectory(); } - private void loadSecpProxySigners( + private void loadProxySigners( final KeystoresParameters keystoreParameter, final String identifier, - final Path identifierPath) { - final Path proxySecpDir = identifierPath.resolve(SECP256K1.name()); - if (canReadFromDirectory(proxySecpDir)) { - // load secp proxy signers (compressed) - final MappedResults secpSignersResults = - SecpV3KeystoresBulkLoader.loadV3KeystoresUsingPasswordFileOrDir( - proxySecpDir, keystoreParameter.getKeystoresPasswordFile(), true); - final Collection secpSigners = secpSignersResults.getValues(); - proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(secpSigners); - } - } - - private void loadBlsProxySigners( - final KeystoresParameters keystoreParameter, - final String identifier, - final Path identifierPath) { - final Path proxyBlsDir = identifierPath.resolve(BLS.name()); - - if (canReadFromDirectory(proxyBlsDir)) { - // load bls proxy signers - final BlsKeystoreBulkLoader blsKeystoreBulkLoader = new BlsKeystoreBulkLoader(); - final MappedResults blsSignersResult = - blsKeystoreBulkLoader.loadKeystoresUsingPasswordFile( - proxyBlsDir, keystoreParameter.getKeystoresPasswordFile()); - final Collection blsSigners = blsSignersResult.getValues(); - proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(blsSigners); + final String keyType, + final BiFunction> loaderFunction) { + + // Calculate identifierPath from keystoreParameter + final Path identifierPath = keystoreParameter.getKeystoresPath().resolve(identifier); + final Path proxyDir = identifierPath.resolve(keyType); + + if (canReadFromDirectory(proxyDir)) { + final MappedResults signersResult = + loaderFunction.apply(proxyDir, keystoreParameter.getKeystoresPasswordFile()); + final Collection signers = signersResult.getValues(); + proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(signers); } } } From 3f9e82c71c8f51ed591724da861497acd3aa4a6a Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 24 Oct 2024 16:39:33 +1000 Subject: [PATCH 24/57] default gvr to zero for minimal networks --- .../commandline/subcommands/Eth2SubCommand.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 f7dd2590b..ac8bc7139 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 @@ -278,8 +278,23 @@ protected void validateArgs() { commitBoostApiParameters.validateParameters(); } + /** + * Validates the genesis state and initializes the genesis validator root. For minimal networks, + * which is used in tests, we use 0 as gvr value. + * + * @param eth2NetworkConfig the network configuration + */ private void validateAndInitGenesisValidatorRoot( final Eth2NetworkConfiguration eth2NetworkConfig) { + + if ((Eth2Network.MINIMAL.configName().equals(network) + || Eth2Network.SWIFT.configName().equals(network) + || Eth2Network.LESS_SWIFT.configName().equals(network)) + && StringUtils.isBlank(genesisState)) { + this.genesisValidatorRoot = Bytes32.ZERO; + return; + } + final String genesisState = eth2NetworkConfig .getNetworkBoostrapConfig() From b9360e148cee05910629f885312e40ec345264ad Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 25 Oct 2024 11:37:39 +1000 Subject: [PATCH 25/57] refactor eth2 network override to mixin class --- .../commandline/PicoCliNetworkOverrides.java | 99 +++++++++++++ .../subcommands/Eth2SubCommand.java | 135 +++--------------- 2 files changed, 122 insertions(+), 112 deletions(-) create mode 100644 commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java new file mode 100644 index 000000000..b20bc8c82 --- /dev/null +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java @@ -0,0 +1,99 @@ +/* + * 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.commandline; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networks.Eth2NetworkConfiguration; +import tech.pegasys.web3signer.commandline.subcommands.Eth2SubCommand; + +import picocli.CommandLine; + +/** Mixin class to hold network overrides for the PicoCLI parser. */ +public class PicoCliNetworkOverrides { + @CommandLine.Option( + names = {"--Xnetwork-altair-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Altair fork activation epoch.", + arity = "1", + converter = Eth2SubCommand.UInt64Converter.class) + private UInt64 altairForkEpoch; + + @CommandLine.Option( + names = {"--Xnetwork-bellatrix-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Bellatrix fork activation epoch.", + arity = "1", + converter = Eth2SubCommand.UInt64Converter.class) + private UInt64 bellatrixForkEpoch; + + @CommandLine.Option( + names = {"--Xnetwork-capella-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Capella fork activation epoch.", + arity = "1", + converter = Eth2SubCommand.UInt64Converter.class) + private UInt64 capellaForkEpoch; + + @CommandLine.Option( + names = {"--Xnetwork-deneb-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Deneb fork activation epoch.", + arity = "1", + converter = Eth2SubCommand.UInt64Converter.class) + private UInt64 denebForkEpoch; + + @CommandLine.Option( + names = {"--Xnetwork-electra-fork-epoch"}, + hidden = true, + paramLabel = "", + description = "Override the Electra fork activation epoch.", + arity = "1", + converter = Eth2SubCommand.UInt64Converter.class) + private UInt64 electraForkEpoch; + + @CommandLine.Option( + names = {"--Xtrusted-setup"}, + hidden = true, + paramLabel = "", + description = + "The trusted setup which is needed for KZG commitments. Only required when creating a custom network. This value should be a file or URL pointing to a trusted setup.", + arity = "1") + private String trustedSetup = null; // Depends on network configuration + + public Eth2NetworkConfiguration.Builder applyOverrides( + final Eth2NetworkConfiguration.Builder builder) { + if (altairForkEpoch != null) { + builder.altairForkEpoch(altairForkEpoch); + } + if (bellatrixForkEpoch != null) { + builder.bellatrixForkEpoch(bellatrixForkEpoch); + } + if (capellaForkEpoch != null) { + builder.capellaForkEpoch(capellaForkEpoch); + } + if (denebForkEpoch != null) { + builder.denebForkEpoch(denebForkEpoch); + } + if (electraForkEpoch != null) { + builder.electraForkEpoch(electraForkEpoch); + } + if (trustedSetup != null) { + builder.trustedSetup(trustedSetup); + } + return builder; + } +} 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 ac8bc7139..a6fca8154 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 @@ -30,6 +30,7 @@ import tech.pegasys.web3signer.commandline.PicoCliAwsSecretsManagerParameters; import tech.pegasys.web3signer.commandline.PicoCliEth2AzureKeyVaultParameters; import tech.pegasys.web3signer.commandline.PicoCliGcpSecretManagerParameters; +import tech.pegasys.web3signer.commandline.PicoCliNetworkOverrides; import tech.pegasys.web3signer.commandline.PicoCliSlashingProtectionParameters; import tech.pegasys.web3signer.commandline.config.PicoCommitBoostApiParameters; import tech.pegasys.web3signer.commandline.config.PicoKeystoresParameters; @@ -48,7 +49,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; @@ -105,60 +105,6 @@ private static class NetworkCliCompletionCandidates extends ArrayList { arity = "1") private String genesisState; - @CommandLine.Option( - names = {"--Xnetwork-altair-fork-epoch"}, - hidden = true, - paramLabel = "", - description = "Override the Altair fork activation epoch.", - arity = "1", - converter = UInt64Converter.class) - private UInt64 altairForkEpoch; - - @CommandLine.Option( - names = {"--Xnetwork-bellatrix-fork-epoch"}, - hidden = true, - paramLabel = "", - description = "Override the Bellatrix fork activation epoch.", - arity = "1", - converter = UInt64Converter.class) - private UInt64 bellatrixForkEpoch; - - @CommandLine.Option( - names = {"--Xnetwork-capella-fork-epoch"}, - hidden = true, - paramLabel = "", - description = "Override the Capella fork activation epoch.", - arity = "1", - converter = UInt64Converter.class) - private UInt64 capellaForkEpoch; - - @CommandLine.Option( - names = {"--Xnetwork-deneb-fork-epoch"}, - hidden = true, - paramLabel = "", - description = "Override the Deneb fork activation epoch.", - arity = "1", - converter = UInt64Converter.class) - private UInt64 denebForkEpoch; - - @CommandLine.Option( - names = {"--Xnetwork-electra-fork-epoch"}, - hidden = true, - paramLabel = "", - description = "Override the Electra fork activation epoch.", - arity = "1", - converter = UInt64Converter.class) - private UInt64 electraForkEpoch; - - @CommandLine.Option( - names = {"--Xtrusted-setup"}, - hidden = true, - paramLabel = "", - description = - "The trusted setup which is needed for KZG commitments. Only required when creating a custom network. This value should be a file or URL pointing to a trusted setup.", - arity = "1") - private String trustedSetup = null; // Depends on network configuration - @CommandLine.Option( names = {"--key-manager-api-enabled", "--enable-key-manager-api"}, paramLabel = "", @@ -174,13 +120,16 @@ private static class NetworkCliCompletionCandidates extends ArrayList { hidden = true) private boolean signingExtEnabled = false; + @Mixin private PicoCliNetworkOverrides networkOverrides; @Mixin private PicoCliSlashingProtectionParameters slashingProtectionParameters; @Mixin private PicoCliEth2AzureKeyVaultParameters azureKeyVaultParameters; @Mixin private PicoKeystoresParameters keystoreParameters; @Mixin private PicoCliAwsSecretsManagerParameters awsSecretsManagerParameters; @Mixin private PicoCliGcpSecretManagerParameters gcpSecretManagerParameters; @Mixin private PicoCommitBoostApiParameters commitBoostApiParameters; - private tech.pegasys.teku.spec.Spec eth2Spec; + + // eth2 network configuration is initialized during validation + private Eth2NetworkConfiguration eth2NetworkConfig; private Bytes32 genesisValidatorRoot; public Eth2SubCommand() { @@ -198,7 +147,7 @@ public Runner createRunner() { keystoreParameters, awsSecretsManagerParameters, gcpSecretManagerParameters, - eth2Spec, + eth2NetworkConfig.getSpec(), genesisValidatorRoot, isKeyManagerApiEnabled, signingExtEnabled, @@ -206,7 +155,7 @@ public Runner createRunner() { } private void logNetworkSpecInformation() { - final ForkSchedule forkSchedule = eth2Spec.getForkSchedule(); + final ForkSchedule forkSchedule = eth2NetworkConfig.getSpec().getForkSchedule(); final Map milestoneSlotMap = forkSchedule .streamMilestoneBoundarySlots() @@ -228,36 +177,23 @@ private void logNetworkSpecInformation() { LOG.info(logString); } - private Eth2NetworkConfiguration createEth2NetworkConfig() { - Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); - builder.applyNetworkDefaults(network); - if (altairForkEpoch != null) { - builder.altairForkEpoch(altairForkEpoch); - } - if (bellatrixForkEpoch != null) { - builder.bellatrixForkEpoch(bellatrixForkEpoch); - } - if (capellaForkEpoch != null) { - builder.capellaForkEpoch(capellaForkEpoch); - } - if (denebForkEpoch != null) { - builder.denebForkEpoch(denebForkEpoch); - } - if (electraForkEpoch != null) { - builder.electraForkEpoch(electraForkEpoch); - } - if (trustedSetup != null) { - builder.trustedSetup(trustedSetup); - } - if (StringUtils.isNotBlank(genesisState)) { - builder.customGenesisState(genesisState); + private Eth2NetworkConfiguration createEth2NetworkConfig() throws ParameterException { + try { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + builder.applyNetworkDefaults(network); + return networkOverrides.applyOverrides(builder).build(); + } catch (final IllegalArgumentException e) { + throw new ParameterException( + commandSpec.commandLine(), + "Failed to load network " + network + " due to " + e.getMessage(), + e); } - return builder.build(); } @Override protected void validateArgs() { - validateAndInitGenesisValidatorRoot(validateAndInitSpec()); + this.eth2NetworkConfig = createEth2NetworkConfig(); + this.genesisValidatorRoot = genesisValidatorsRoot(); if (slashingProtectionParameters.isEnabled() && slashingProtectionParameters.getDbUrl() == null) { @@ -281,20 +217,8 @@ protected void validateArgs() { /** * Validates the genesis state and initializes the genesis validator root. For minimal networks, * which is used in tests, we use 0 as gvr value. - * - * @param eth2NetworkConfig the network configuration */ - private void validateAndInitGenesisValidatorRoot( - final Eth2NetworkConfiguration eth2NetworkConfig) { - - if ((Eth2Network.MINIMAL.configName().equals(network) - || Eth2Network.SWIFT.configName().equals(network) - || Eth2Network.LESS_SWIFT.configName().equals(network)) - && StringUtils.isBlank(genesisState)) { - this.genesisValidatorRoot = Bytes32.ZERO; - return; - } - + private Bytes32 genesisValidatorsRoot() { final String genesisState = eth2NetworkConfig .getNetworkBoostrapConfig() @@ -307,8 +231,9 @@ private void validateAndInitGenesisValidatorRoot( + network + ". Please provide a valid genesis state resource.")); try { - final BeaconState beaconState = ChainDataLoader.loadState(eth2Spec, genesisState); - this.genesisValidatorRoot = beaconState.getGenesisValidatorsRoot(); + final BeaconState beaconState = + ChainDataLoader.loadState(eth2NetworkConfig.getSpec(), genesisState); + return beaconState.getGenesisValidatorsRoot(); } catch (final IOException | NullPointerException e) { throw new ParameterException( commandSpec.commandLine(), @@ -321,20 +246,6 @@ private void validateAndInitGenesisValidatorRoot( } } - private Eth2NetworkConfiguration validateAndInitSpec() { - final Eth2NetworkConfiguration eth2NetworkConfig; - try { - eth2NetworkConfig = createEth2NetworkConfig(); - eth2Spec = eth2NetworkConfig.getSpec(); - } catch (final IllegalArgumentException e) { - throw new ParameterException( - commandSpec.commandLine(), - "Failed to load network " + network + " due to " + e.getMessage(), - e); - } - return eth2NetworkConfig; - } - private void validateGcpSecretManagerParameters() { if (gcpSecretManagerParameters.isEnabled()) { final List specifiedAuthModeMissingFields = From 81611e6e088a4d09fda94613bf949e41b0367d70 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 25 Oct 2024 12:22:10 +1000 Subject: [PATCH 26/57] Refactor to specify genesis validators root in commit boost api parameters --- .../config/PicoCommitBoostApiParameters.java | 81 +++++++++++++++---- .../convertor/Bytes32Converter.java | 27 +++++++ .../subcommands/Eth2SubCommand.java | 49 +---------- .../pegasys/web3signer/core/Eth2Runner.java | 18 ++--- .../CommitBoostGenerateProxyKeyRoute.java | 17 ++-- .../CommitBoostGenerateProxyKeyHandler.java | 13 ++- .../commitboost/ProxyKeyGenerator.java | 16 ++-- .../signing/config/CommitBoostParameters.java | 48 +++++++++++ .../config/DefaultArtifactSignerProvider.java | 16 ++-- .../DefaultArtifactSignerProviderTest.java | 21 ++--- 10 files changed, 188 insertions(+), 118 deletions(-) create mode 100644 commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java create mode 100644 signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java index cf2c4293d..e7e05bc9c 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java @@ -14,17 +14,22 @@ import static tech.pegasys.web3signer.commandline.DefaultCommandValues.PATH_FORMAT_HELP; -import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.teku.networks.Eth2NetworkConfiguration; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; +import tech.pegasys.web3signer.commandline.convertor.Bytes32Converter; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import java.nio.file.Path; +import org.apache.tuweni.bytes.Bytes32; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; import picocli.CommandLine.Option; import picocli.CommandLine.ParameterException; import picocli.CommandLine.Spec; -public class PicoCommitBoostApiParameters implements KeystoresParameters { +public class PicoCommitBoostApiParameters implements CommitBoostParameters { @Spec private CommandSpec commandSpec; // injected by picocli @CommandLine.Option( @@ -39,50 +44,96 @@ public class PicoCommitBoostApiParameters implements KeystoresParameters { description = "The path to a writeable directory to store v3 and v4 proxy keystores for commit boost API.", paramLabel = PATH_FORMAT_HELP) - private Path keystoresPath; + private Path proxyKeystoresPath; @Option( names = {"--proxy-keystores-password-file"}, description = "The path to the password file used to encrypt/decrypt proxy keystores for commit boost API.", paramLabel = PATH_FORMAT_HELP) - private Path keystoresPasswordFile; + private Path proxyKeystoresPasswordFile; + + @Option( + names = {"-Xgenesis-validators-root"}, + description = + "Override genesis validators root for the network. Use for custom network configurations.", + converter = Bytes32Converter.class, + paramLabel = "") + private Bytes32 genesisValidatorsRoot; @Override - public Path getKeystoresPath() { - return keystoresPath; + public boolean isEnabled() { + return isCommitBoostApiEnabled; } @Override - public Path getKeystoresPasswordsPath() { - return null; + public Path getProxyKeystoresPath() { + return proxyKeystoresPath; } @Override - public Path getKeystoresPasswordFile() { - return keystoresPasswordFile; + public Path getProxyKeystoresPasswordFile() { + return proxyKeystoresPasswordFile; } @Override - public boolean isEnabled() { - return isCommitBoostApiEnabled; + public Bytes32 getGenesisValidatorsRoot() { + if (genesisValidatorsRoot == null) { + throw new IllegalStateException("Genesis validators root not set"); + } + return genesisValidatorsRoot; } - public void validateParameters() throws ParameterException { + /** + * Validate the parameters for the commit boost API and initialize parameters which will be used + * during run operation. + */ + public void validateParameters(final Eth2NetworkConfiguration eth2NetworkConfig) + throws ParameterException { if (!isCommitBoostApiEnabled) { return; } - if (keystoresPath == null) { + if (proxyKeystoresPath == null) { throw new ParameterException( commandSpec.commandLine(), "Commit boost API is enabled, but --proxy-keystores-path not set"); } - if (keystoresPasswordFile == null) { + if (proxyKeystoresPasswordFile == null) { throw new ParameterException( commandSpec.commandLine(), "Commit boost API is enabled, but --proxy-keystores-password-file not set"); } + + loadGenesisValidatorsRoot(eth2NetworkConfig); + } + + /** If genesis validators root is not provided, load genesis state and genesis validators root. */ + private void loadGenesisValidatorsRoot(final Eth2NetworkConfiguration eth2NetworkConfig) { + if (genesisValidatorsRoot != null) { + return; + } + + final String genesisState = + eth2NetworkConfig + .getNetworkBoostrapConfig() + .getGenesisState() + .orElseThrow( + () -> + new ParameterException( + commandSpec.commandLine(), + "Unable to load genesis state to determine genesis validators root. " + + "Please provide genesis validators root using --Xgenesis-validators-root")); + try { + final BeaconState beaconState = + ChainDataLoader.loadState(eth2NetworkConfig.getSpec(), genesisState); + genesisValidatorsRoot = beaconState.getGenesisValidatorsRoot(); + } catch (final Exception e) { + throw new ParameterException( + commandSpec.commandLine(), + "Unable to load genesis state to determine genesis validators root. " + + "Please provide genesis validators root using --Xgenesis-validators-root"); + } } } diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java new file mode 100644 index 000000000..10c38d3b7 --- /dev/null +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java @@ -0,0 +1,27 @@ +/* + * 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.commandline.convertor; + +import org.apache.tuweni.bytes.Bytes32; +import picocli.CommandLine; + +public class Bytes32Converter implements CommandLine.ITypeConverter { + @Override + public Bytes32 convert(final String hexString) throws Exception { + try { + return Bytes32.fromHexString(hexString); + } catch (final Exception e) { + throw new CommandLine.TypeConversionException("Invalid hex string: " + hexString); + } + } +} 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 a6fca8154..55e34b88c 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 @@ -24,8 +24,6 @@ import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.ForkSchedule; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.web3signer.commandline.PicoCliAwsSecretsManagerParameters; import tech.pegasys.web3signer.commandline.PicoCliEth2AzureKeyVaultParameters; @@ -40,7 +38,6 @@ import tech.pegasys.web3signer.signing.config.KeystoresParameters; import tech.pegasys.web3signer.slashingprotection.SlashingProtectionParameters; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -51,7 +48,6 @@ import com.google.common.collect.Lists; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes32; import picocli.CommandLine; import picocli.CommandLine.Command; import picocli.CommandLine.HelpCommand; @@ -96,15 +92,6 @@ private static class NetworkCliCompletionCandidates extends ArrayList { arity = "1") private String network; - @CommandLine.Option( - names = {"--genesis-state"}, - paramLabel = "", - description = - "The genesis state. This value should be a file or URL pointing to an SSZ-encoded finalized checkpoint " - + "state.", - arity = "1") - private String genesisState; - @CommandLine.Option( names = {"--key-manager-api-enabled", "--enable-key-manager-api"}, paramLabel = "", @@ -130,7 +117,6 @@ private static class NetworkCliCompletionCandidates extends ArrayList { // eth2 network configuration is initialized during validation private Eth2NetworkConfiguration eth2NetworkConfig; - private Bytes32 genesisValidatorRoot; public Eth2SubCommand() { network = "mainnet"; @@ -148,7 +134,6 @@ public Runner createRunner() { awsSecretsManagerParameters, gcpSecretManagerParameters, eth2NetworkConfig.getSpec(), - genesisValidatorRoot, isKeyManagerApiEnabled, signingExtEnabled, commitBoostApiParameters); @@ -193,7 +178,6 @@ private Eth2NetworkConfiguration createEth2NetworkConfig() throws ParameterExcep @Override protected void validateArgs() { this.eth2NetworkConfig = createEth2NetworkConfig(); - this.genesisValidatorRoot = genesisValidatorsRoot(); if (slashingProtectionParameters.isEnabled() && slashingProtectionParameters.getDbUrl() == null) { @@ -211,39 +195,8 @@ protected void validateArgs() { validateKeystoreParameters(keystoreParameters); validateAwsSecretsManageParameters(); validateGcpSecretManagerParameters(); - commitBoostApiParameters.validateParameters(); - } - /** - * Validates the genesis state and initializes the genesis validator root. For minimal networks, - * which is used in tests, we use 0 as gvr value. - */ - private Bytes32 genesisValidatorsRoot() { - final String genesisState = - eth2NetworkConfig - .getNetworkBoostrapConfig() - .getGenesisState() - .orElseThrow( - () -> - new ParameterException( - commandSpec.commandLine(), - "Unable to load genesis state for network " - + network - + ". Please provide a valid genesis state resource.")); - try { - final BeaconState beaconState = - ChainDataLoader.loadState(eth2NetworkConfig.getSpec(), genesisState); - return beaconState.getGenesisValidatorsRoot(); - } catch (final IOException | NullPointerException e) { - throw new ParameterException( - commandSpec.commandLine(), - "Unable to load genesis state for network " - + network - + " from " - + genesisState - + ": " - + e.getMessage()); - } + commitBoostApiParameters.validateParameters(this.eth2NetworkConfig); } private void validateGcpSecretManagerParameters() { 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 5bc367802..cf953367b 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/Eth2Runner.java @@ -44,6 +44,7 @@ import tech.pegasys.web3signer.signing.config.AwsVaultParameters; import tech.pegasys.web3signer.signing.config.AzureKeyVaultFactory; import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -88,10 +89,9 @@ public class Eth2Runner extends Runner { private final boolean pruningEnabled; private final KeystoresParameters keystoresParameters; private final Spec eth2Spec; - private final Bytes32 genesisValidatorsRoot; private final boolean isKeyManagerApiEnabled; private final boolean signingExtEnabled; - private final KeystoresParameters commitBoostApiParameters; + private final CommitBoostParameters commitBoostParameters; public Eth2Runner( final BaseConfig baseConfig, @@ -101,10 +101,9 @@ public Eth2Runner( final AwsVaultParameters awsVaultParameters, final GcpSecretManagerParameters gcpSecretManagerParameters, final Spec eth2Spec, - final Bytes32 genesisValidatorsRoot, final boolean isKeyManagerApiEnabled, final boolean signingExtEnabled, - final KeystoresParameters commitBoostApiParameters) { + final CommitBoostParameters commitBoostParameters) { super(baseConfig); this.slashingProtectionContext = createSlashingProtection(slashingProtectionParameters); this.azureKeyVaultParameters = azureKeyVaultParameters; @@ -112,12 +111,11 @@ public Eth2Runner( this.pruningEnabled = slashingProtectionParameters.isPruningEnabled(); this.keystoresParameters = keystoresParameters; this.eth2Spec = eth2Spec; - this.genesisValidatorsRoot = genesisValidatorsRoot; this.isKeyManagerApiEnabled = isKeyManagerApiEnabled; this.awsVaultParameters = awsVaultParameters; this.gcpSecretManagerParameters = gcpSecretManagerParameters; this.signingExtEnabled = signingExtEnabled; - this.commitBoostApiParameters = commitBoostApiParameters; + this.commitBoostParameters = commitBoostParameters; } private Optional createSlashingProtection( @@ -145,11 +143,9 @@ public void populateRouter(final Context context) { if (isKeyManagerApiEnabled) { new KeyManagerApiRoute(context, baseConfig, slashingProtectionContext).register(); } - if (commitBoostApiParameters.isEnabled()) { + if (commitBoostParameters.isEnabled()) { new CommitBoostPublicKeysRoute(context).register(); - new CommitBoostGenerateProxyKeyRoute( - context, commitBoostApiParameters, eth2Spec, genesisValidatorsRoot) - .register(); + new CommitBoostGenerateProxyKeyRoute(context, commitBoostParameters, eth2Spec).register(); } } @@ -181,7 +177,7 @@ protected List createArtifactSignerProvider( return signers; } }, - Optional.of(commitBoostApiParameters))); + Optional.of(commitBoostParameters))); } private MappedResults loadSignersFromKeyConfigFiles( diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java index 6770a4e26..bd5156ed4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java @@ -21,30 +21,26 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; import tech.pegasys.web3signer.signing.BlsArtifactSignature; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; -import tech.pegasys.web3signer.signing.config.KeystoresParameters; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; -import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyRoute implements Web3SignerRoute { private static final String PATH = "/signer/v1/generate_proxy_key"; private final Context context; private final SignerForIdentifier blsSigner; - private final KeystoresParameters commitBoostApiParameters; + private final CommitBoostParameters commitBoostParameters; private final Spec eth2Spec; - private final Bytes32 genesisValidatorsRoot; public CommitBoostGenerateProxyKeyRoute( final Context context, - final KeystoresParameters commitBoostApiParameters, - final Spec eth2Spec, - final Bytes32 genesisValidatorsRoot) { + final CommitBoostParameters commitBoostParameters, + final Spec eth2Spec) { this.context = context; - this.commitBoostApiParameters = commitBoostApiParameters; + this.commitBoostParameters = commitBoostParameters; this.eth2Spec = eth2Spec; - this.genesisValidatorsRoot = genesisValidatorsRoot; // there should be only one DefaultArtifactSignerProvider in eth2 mode final ArtifactSignerProvider artifactSignerProvider = @@ -64,8 +60,7 @@ public void register() { .getRouter() .route(HttpMethod.POST, PATH) .blockingHandler( - new CommitBoostGenerateProxyKeyHandler( - blsSigner, commitBoostApiParameters, eth2Spec, genesisValidatorsRoot), + new CommitBoostGenerateProxyKeyHandler(blsSigner, commitBoostParameters, eth2Spec), false) .failureHandler(context.getErrorHandler()) .failureHandler( diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 20d49d881..bc3580967 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -23,7 +23,7 @@ import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.signing.ArtifactSigner; -import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import java.util.Optional; @@ -33,7 +33,6 @@ import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); @@ -48,12 +47,12 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler signerForIdentifier, - final KeystoresParameters commitBoostApiParameters, - final Spec eth2Spec, - final Bytes32 genesisValidatorsRoot) { + final CommitBoostParameters commitBoostParameters, + final Spec eth2Spec) { this.signerForIdentifier = signerForIdentifier; - this.proxyKeyGenerator = new ProxyKeyGenerator(commitBoostApiParameters); - this.signingRootGenerator = new SigningRootGenerator(eth2Spec, genesisValidatorsRoot); + this.proxyKeyGenerator = new ProxyKeyGenerator(commitBoostParameters); + this.signingRootGenerator = + new SigningRootGenerator(eth2Spec, commitBoostParameters.getGenesisValidatorsRoot()); } @Override diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index 8a90c3bca..e0cb14151 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -26,7 +26,7 @@ import tech.pegasys.web3signer.signing.BlsArtifactSigner; import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; import tech.pegasys.web3signer.signing.KeyType; -import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.metadata.SignerOrigin; import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; @@ -52,10 +52,10 @@ public class ProxyKeyGenerator { private static final Logger LOG = LogManager.getLogger(); private final SecureRandom secureRandom = new SecureRandom(); - private final KeystoresParameters commitBoostApiParameters; + private final CommitBoostParameters commitBoostParameters; - public ProxyKeyGenerator(final KeystoresParameters commitBoostApiParameters) { - this.commitBoostApiParameters = commitBoostApiParameters; + public ProxyKeyGenerator(final CommitBoostParameters commitBoostParameters) { + this.commitBoostParameters = commitBoostParameters; } public ArtifactSigner generateECProxyKey(final String identifier) @@ -80,14 +80,14 @@ private Path createBLSKeystoreFile(final BLSKeyPair keyPair, final String identi final KdfParam kdfParam = new Pbkdf2Param(32, counter, HMAC_SHA256, salt); final Cipher cipher = new Cipher(CipherFunction.AES_128_CTR, iv); final Bytes48 publicKey = keyPair.getPublicKey().toBytesCompressed(); - final String password = readFile(commitBoostApiParameters.getKeystoresPasswordFile()); + final String password = readFile(commitBoostParameters.getProxyKeystoresPasswordFile()); final KeyStoreData keyStoreData = KeyStore.encrypt( keyPair.getSecretKey().toBytes(), publicKey, password, "", kdfParam, cipher); try { final Path keystoreDir = createSubDirectories( - commitBoostApiParameters.getKeystoresPath(), identifier, KeyType.BLS); + commitBoostParameters.getProxyKeystoresPath(), identifier, KeyType.BLS); final Path keystoreFile = keystoreDir.resolve(publicKey + ".json"); KeyStoreLoader.saveToFile(keystoreFile, keyStoreData); return keystoreFile; @@ -97,10 +97,10 @@ private Path createBLSKeystoreFile(final BLSKeyPair keyPair, final String identi } private Path createECWalletFile(final ECKeyPair ecKeyPair, final String identifier) { - final String password = readFile(commitBoostApiParameters.getKeystoresPasswordFile()); + final String password = readFile(commitBoostParameters.getProxyKeystoresPasswordFile()); final Path keystoreDir = createSubDirectories( - commitBoostApiParameters.getKeystoresPath(), identifier, KeyType.SECP256K1); + commitBoostParameters.getProxyKeystoresPath(), identifier, KeyType.SECP256K1); final String fileName; try { fileName = WalletUtils.generateWalletFile(password, ecKeyPair, keystoreDir.toFile(), true); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java new file mode 100644 index 000000000..e0e6dd4ca --- /dev/null +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java @@ -0,0 +1,48 @@ +/* + * Copyright 2022 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.signing.config; + +import java.nio.file.Path; + +import org.apache.tuweni.bytes.Bytes32; + +/** Configuration parameters for the commit boost API. */ +public interface CommitBoostParameters { + /** + * Whether the commit boost API is enabled. + * + * @return true if enabled, false otherwise + */ + boolean isEnabled(); + + /** + * The path to a writeable directory to store proxy BLS and SECP keystores for commit boost API. + * + * @return the path to the directory + */ + Path getProxyKeystoresPath(); + + /** + * The path to the password file used to encrypt/decrypt proxy keystores for commit boost API. + * + * @return the path to the password file + */ + Path getProxyKeystoresPasswordFile(); + + /** + * The Genesis Validators Root for the network used by the commit boost signing operations. + * + * @return Genesis Validators Root as Bytes32 + */ + Bytes32 getGenesisValidatorsRoot(); +} diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 534ca6709..61afc52e3 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -49,13 +49,13 @@ public class DefaultArtifactSignerProvider implements ArtifactSignerProvider { private final Map signers = new HashMap<>(); private final Map> proxySigners = new HashMap<>(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); - private final Optional commitBoostKeystoresParameters; + private final Optional commitBoostParameters; public DefaultArtifactSignerProvider( final Supplier> artifactSignerCollectionSupplier, - final Optional commitBoostKeystoresParameters) { + final Optional commitBoostParameters) { this.artifactSignerCollectionSupplier = artifactSignerCollectionSupplier; - this.commitBoostKeystoresParameters = commitBoostKeystoresParameters; + this.commitBoostParameters = commitBoostParameters; } @Override @@ -77,8 +77,8 @@ public Future load() { .forEach(signers::putIfAbsent); // for each loaded signer, load commit boost proxy signers (if any) - commitBoostKeystoresParameters - .filter(KeystoresParameters::isEnabled) + commitBoostParameters + .filter(CommitBoostParameters::isEnabled) .ifPresent( keystoreParameter -> signers @@ -175,18 +175,18 @@ private static boolean canReadFromDirectory(final Path path) { } private void loadProxySigners( - final KeystoresParameters keystoreParameter, + final CommitBoostParameters keystoreParameter, final String identifier, final String keyType, final BiFunction> loaderFunction) { // Calculate identifierPath from keystoreParameter - final Path identifierPath = keystoreParameter.getKeystoresPath().resolve(identifier); + final Path identifierPath = keystoreParameter.getProxyKeystoresPath().resolve(identifier); final Path proxyDir = identifierPath.resolve(keyType); if (canReadFromDirectory(proxyDir)) { final MappedResults signersResult = - loaderFunction.apply(proxyDir, keystoreParameter.getKeystoresPasswordFile()); + loaderFunction.apply(proxyDir, keystoreParameter.getProxyKeystoresPath()); final Collection signers = signersResult.getValues(); proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(signers); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 30a9757fa..a440f8318 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -36,6 +36,7 @@ import java.util.Optional; import java.util.stream.IntStream; +import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -112,7 +113,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { final SecureRandom secureRandom = new SecureRandom(); // create random proxy signers - final KeystoresParameters commitBoostParameters = + final CommitBoostParameters commitBoostParameters = new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); // create random BLS key pairs as proxy keys for public key1 and public key2 @@ -157,7 +158,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { @Test void emptyProxySignersAreLoadedSuccessfully() { // enable commit boost without existing proxy keys - final KeystoresParameters commitBoostParameters = + final CommitBoostParameters commitBoostParameters = new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); // set up mock signers @@ -239,7 +240,7 @@ private static String[] getCompressedSecpPublicKeysArray(final List e .toArray(String[]::new); } - private static class TestCommitBoostParameters implements KeystoresParameters { + private static class TestCommitBoostParameters implements CommitBoostParameters { private final Path keystorePath; private final Path passwordFile; @@ -256,23 +257,23 @@ public TestCommitBoostParameters(final Path keystorePath, final Path passwordDir } @Override - public Path getKeystoresPath() { - return keystorePath; + public boolean isEnabled() { + return true; } @Override - public Path getKeystoresPasswordsPath() { - return null; + public Path getProxyKeystoresPath() { + return keystorePath; } @Override - public Path getKeystoresPasswordFile() { + public Path getProxyKeystoresPasswordFile() { return passwordFile; } @Override - public boolean isEnabled() { - return true; + public Bytes32 getGenesisValidatorsRoot() { + return Bytes32.ZERO; } } } From e3066aaee933dce60f00aea4c8d11976a5fbf37e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 29 Oct 2024 10:30:15 +1000 Subject: [PATCH 27/57] fix password file path in DefaultArtifactSignerProvider --- .../signing/config/DefaultArtifactSignerProvider.java | 2 +- .../signing/config/DefaultArtifactSignerProviderTest.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 61afc52e3..5979bfa11 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -186,7 +186,7 @@ private void loadProxySigners( if (canReadFromDirectory(proxyDir)) { final MappedResults signersResult = - loaderFunction.apply(proxyDir, keystoreParameter.getProxyKeystoresPath()); + loaderFunction.apply(proxyDir, keystoreParameter.getProxyKeystoresPasswordFile()); final Collection signers = signersResult.getValues(); proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(signers); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index a440f8318..7c397e82f 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -116,6 +116,10 @@ void proxySignersAreLoadedCorrectly() throws IOException { final CommitBoostParameters commitBoostParameters = new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); + assertThat(commitBoostParameters.getProxyKeystoresPasswordFile()) + .exists() + .hasFileName("password.txt"); + // create random BLS key pairs as proxy keys for public key1 and public key2 final List key1ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY1); final List key2ProxyKeyPairs = randomBLSV4Keystores(secureRandom, PUBLIC_KEY2); From 9b535c988dc3fbdca0e6439aeb7adcd1aebe47ed Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 29 Oct 2024 10:59:06 +1000 Subject: [PATCH 28/57] Use --genesis-state instead of gvr value from cli --- .../config/PicoCommitBoostApiParameters.java | 45 +++++++++++-------- .../subcommands/Eth2SubCommand.java | 44 ++++++++++++------ 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java index e7e05bc9c..541cce19f 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java @@ -17,7 +17,6 @@ import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; -import tech.pegasys.web3signer.commandline.convertor.Bytes32Converter; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import java.nio.file.Path; @@ -53,12 +52,17 @@ public class PicoCommitBoostApiParameters implements CommitBoostParameters { paramLabel = PATH_FORMAT_HELP) private Path proxyKeystoresPasswordFile; + // note: In the future, this can be moved to PicoCliNetworkOverrides if we want to determine gvr + // for slashing protection etc. in advance before getting the first signing call from CL. @Option( - names = {"-Xgenesis-validators-root"}, + names = {"--genesis-state"}, + paramLabel = "", description = - "Override genesis validators root for the network. Use for custom network configurations.", - converter = Bytes32Converter.class, - paramLabel = "") + "The genesis state. This value should be a file or URL pointing to an SSZ-encoded finalized checkpoint " + + "state. It is used to determine the value of genesis validators root for Commit Boost API.", + arity = "1") + private String genesisState; + private Bytes32 genesisValidatorsRoot; @Override @@ -84,6 +88,20 @@ public Bytes32 getGenesisValidatorsRoot() { return genesisValidatorsRoot; } + /** + * Apply genesis state overrides to the network configuration only if commit boost API is enabled. + * + * @param builder The network configuration builder to apply overrides to. + * @return The network configuration builder with overrides applied. + */ + public Eth2NetworkConfiguration.Builder applyOverrides( + final Eth2NetworkConfiguration.Builder builder) { + if (isCommitBoostApiEnabled) { + builder.customGenesisState(genesisState); + } + return builder; + } + /** * Validate the parameters for the commit boost API and initialize parameters which will be used * during run operation. @@ -111,29 +129,20 @@ public void validateParameters(final Eth2NetworkConfiguration eth2NetworkConfig) /** If genesis validators root is not provided, load genesis state and genesis validators root. */ private void loadGenesisValidatorsRoot(final Eth2NetworkConfiguration eth2NetworkConfig) { - if (genesisValidatorsRoot != null) { - return; - } - + final String parameterExceptionMessage = + "Unable to load genesis state to determine genesis validators root. Please provide custom genesis state using --genesis-state"; final String genesisState = eth2NetworkConfig .getNetworkBoostrapConfig() .getGenesisState() .orElseThrow( - () -> - new ParameterException( - commandSpec.commandLine(), - "Unable to load genesis state to determine genesis validators root. " - + "Please provide genesis validators root using --Xgenesis-validators-root")); + () -> new ParameterException(commandSpec.commandLine(), parameterExceptionMessage)); try { final BeaconState beaconState = ChainDataLoader.loadState(eth2NetworkConfig.getSpec(), genesisState); genesisValidatorsRoot = beaconState.getGenesisValidatorsRoot(); } catch (final Exception e) { - throw new ParameterException( - commandSpec.commandLine(), - "Unable to load genesis state to determine genesis validators root. " - + "Please provide genesis validators root using --Xgenesis-validators-root"); + throw new ParameterException(commandSpec.commandLine(), parameterExceptionMessage); } } } 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 55e34b88c..438f4647b 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 @@ -162,22 +162,11 @@ private void logNetworkSpecInformation() { LOG.info(logString); } - private Eth2NetworkConfiguration createEth2NetworkConfig() throws ParameterException { - try { - final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); - builder.applyNetworkDefaults(network); - return networkOverrides.applyOverrides(builder).build(); - } catch (final IllegalArgumentException e) { - throw new ParameterException( - commandSpec.commandLine(), - "Failed to load network " + network + " due to " + e.getMessage(), - e); - } - } - @Override protected void validateArgs() { - this.eth2NetworkConfig = createEth2NetworkConfig(); + final Eth2NetworkConfiguration.Builder eth2NetworkConfigBuilder = + createEth2NetworkConfigBuilder(); + this.eth2NetworkConfig = createEth2NetworkConfig(eth2NetworkConfigBuilder); if (slashingProtectionParameters.isEnabled() && slashingProtectionParameters.getDbUrl() == null) { @@ -199,6 +188,33 @@ protected void validateArgs() { commitBoostApiParameters.validateParameters(this.eth2NetworkConfig); } + private Eth2NetworkConfiguration.Builder createEth2NetworkConfigBuilder() { + try { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + builder.applyNetworkDefaults(network); + networkOverrides.applyOverrides(builder); // custom fork epochs + commitBoostApiParameters.applyOverrides(builder); // genesis state + return builder; + } catch (final IllegalArgumentException e) { + throw new ParameterException( + commandSpec.commandLine(), + "Failed to load network " + network + " due to " + e.getMessage(), + e); + } + } + + private Eth2NetworkConfiguration createEth2NetworkConfig( + final Eth2NetworkConfiguration.Builder builder) throws ParameterException { + try { + return builder.build(); + } catch (final IllegalArgumentException e) { + throw new ParameterException( + commandSpec.commandLine(), + "Failed to load network " + network + " due to " + e.getMessage(), + e); + } + } + private void validateGcpSecretManagerParameters() { if (gcpSecretManagerParameters.isEnabled()) { final List specifiedAuthModeMissingFields = From 4c9a1449671da7ff33cf98634e32ace3726f44bd Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 29 Oct 2024 11:09:36 +1000 Subject: [PATCH 29/57] Fix custom genesis cli option --- .../commandline/PicoCliNetworkOverrides.java | 4 +--- .../config/PicoCommitBoostApiParameters.java | 10 ++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java index b20bc8c82..e9a48cd76 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java @@ -74,8 +74,7 @@ public class PicoCliNetworkOverrides { arity = "1") private String trustedSetup = null; // Depends on network configuration - public Eth2NetworkConfiguration.Builder applyOverrides( - final Eth2NetworkConfiguration.Builder builder) { + public void applyOverrides(final Eth2NetworkConfiguration.Builder builder) { if (altairForkEpoch != null) { builder.altairForkEpoch(altairForkEpoch); } @@ -94,6 +93,5 @@ public Eth2NetworkConfiguration.Builder applyOverrides( if (trustedSetup != null) { builder.trustedSetup(trustedSetup); } - return builder; } } diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java index 541cce19f..6d25d5c5b 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java @@ -21,6 +21,7 @@ import java.nio.file.Path; +import org.apache.commons.lang3.StringUtils; import org.apache.tuweni.bytes.Bytes32; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; @@ -92,14 +93,11 @@ public Bytes32 getGenesisValidatorsRoot() { * Apply genesis state overrides to the network configuration only if commit boost API is enabled. * * @param builder The network configuration builder to apply overrides to. - * @return The network configuration builder with overrides applied. */ - public Eth2NetworkConfiguration.Builder applyOverrides( - final Eth2NetworkConfiguration.Builder builder) { - if (isCommitBoostApiEnabled) { + public void applyOverrides(final Eth2NetworkConfiguration.Builder builder) { + if (isCommitBoostApiEnabled && StringUtils.isNotBlank(genesisState)) { builder.customGenesisState(genesisState); } - return builder; } /** @@ -127,7 +125,7 @@ public void validateParameters(final Eth2NetworkConfiguration eth2NetworkConfig) loadGenesisValidatorsRoot(eth2NetworkConfig); } - /** If genesis validators root is not provided, load genesis state and genesis validators root. */ + /** Load genesis state and obtain genesis validators root. */ private void loadGenesisValidatorsRoot(final Eth2NetworkConfiguration eth2NetworkConfig) { final String parameterExceptionMessage = "Unable to load genesis state to determine genesis validators root. Please provide custom genesis state using --genesis-state"; From eb81e87318ff1c8154809ac389daa1672ce48d83 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 29 Oct 2024 15:31:16 +1000 Subject: [PATCH 30/57] refactor EthPublicKeyUtils methods. Add unit test for ProxykeyGenerator --- ...SecpV3KeystoresBulkLoadAcceptanceTest.java | 2 +- .../publickeys/YubiHsmKeysAcceptanceTest.java | 2 +- .../signing/SecpSigningAcceptanceTest.java | 4 +- .../commitboost/ProxyKeyGenerator.java | 36 ++++-- .../commitboost/SigningRootGenerator.java | 3 +- .../datastructure/SszSECPPublicKey.java | 4 +- .../commitboost/ProxyKeyGeneratorTest.java | 103 ++++++++++++++++++ .../EthSignTransactionResultProviderTest.java | 4 +- .../web3signer/core/util/PublicKeyUtils.java | 2 +- .../signing/secp256k1/EthPublicKeyUtils.java | 16 ++- .../secp256k1/azure/AzureKeyVaultSigner.java | 2 +- .../secp256k1/filebased/CredentialSigner.java | 2 +- .../DefaultArtifactSignerProviderTest.java | 2 +- .../secp256k1/EthPublicKeyUtilsTest.java | 21 ++-- 14 files changed, 165 insertions(+), 38 deletions(-) create mode 100644 core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java index cc33ce011..b9b4b2a9d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/bulkloading/SecpV3KeystoresBulkLoadAcceptanceTest.java @@ -61,7 +61,7 @@ static void initV3Keystores() throws IOException, GeneralSecurityException, Ciph for (int i = 0; i < 4; i++) { final ECKeyPair ecKeyPair = Keys.createEcKeyPair(); final ECPublicKey ecPublicKey = - EthPublicKeyUtils.createPublicKeyFromBigInt(ecKeyPair.getPublicKey()); + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); final String publicKeyHex = IdentifierUtils.normaliseIdentifier(EthPublicKeyUtils.toHexString(ecPublicKey)); publicKeys.add(publicKeyHex); diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java index ba53f54ca..37dec504d 100644 --- a/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/publickeys/YubiHsmKeysAcceptanceTest.java @@ -121,7 +121,7 @@ private void createConfigurationFiles(final Set opaqueDataIds, final Ke private String getPublicKey(final String key) { return normaliseIdentifier( EthPublicKeyUtils.toHexString( - EthPublicKeyUtils.createPublicKeyFromBigInt( + EthPublicKeyUtils.bigIntegerToECPublicKey( Credentials.create(key).getEcKeyPair().getPublicKey()))); } } 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 dacb73763..9660d885c 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 @@ -188,13 +188,13 @@ private void signAndVerifySignature(final String publicKeyHex) { void verifySignature(final Bytes signature, final String publicKeyHex) { final ECPublicKey expectedPublicKey = - EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(publicKeyHex)); + EthPublicKeyUtils.bytesToECPublicKey(Bytes.fromHexString(publicKeyHex)); final byte[] r = signature.slice(0, 32).toArray(); final byte[] s = signature.slice(32, 32).toArray(); final byte[] v = signature.slice(64).toArray(); final BigInteger messagePublicKey = recoverPublicKey(new SignatureData(v, r, s)); - assertThat(EthPublicKeyUtils.createPublicKeyFromBigInt(messagePublicKey)) + assertThat(EthPublicKeyUtils.bigIntegerToECPublicKey(messagePublicKey)) .isEqualTo(expectedPublicKey); } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index e0cb14151..ca9bb35c2 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -22,12 +22,14 @@ import tech.pegasys.teku.bls.keystore.model.KdfParam; import tech.pegasys.teku.bls.keystore.model.KeyStoreData; import tech.pegasys.teku.bls.keystore.model.Pbkdf2Param; +import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.BlsArtifactSigner; import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; import tech.pegasys.web3signer.signing.KeyType; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.metadata.SignerOrigin; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; import java.io.IOException; @@ -37,7 +39,9 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.SecureRandom; +import java.security.interfaces.ECPublicKey; +import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -45,12 +49,15 @@ import org.web3j.crypto.Credentials; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; -import org.web3j.crypto.WalletUtils; +import org.web3j.crypto.Wallet; +import org.web3j.crypto.WalletFile; import org.web3j.crypto.exception.CipherException; /** Proxy Key Generator class to generate proxy keys for CommitBoost API */ public class ProxyKeyGenerator { private static final Logger LOG = LogManager.getLogger(); + private static final ObjectMapper JSON_MAPPER = SigningObjectMapperFactory.createObjectMapper(); + private final SecureRandom secureRandom = new SecureRandom(); private final CommitBoostParameters commitBoostParameters; @@ -58,12 +65,15 @@ public ProxyKeyGenerator(final CommitBoostParameters commitBoostParameters) { this.commitBoostParameters = commitBoostParameters; } - public ArtifactSigner generateECProxyKey(final String identifier) - throws GeneralSecurityException, UncheckedIOException { - final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); - final Path ecWalletFile = createECWalletFile(ecKeyPair, identifier); - LOG.debug("Created proxy EC wallet file {} for identifier: {}", ecWalletFile, identifier); - return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair)), true); + public ArtifactSigner generateECProxyKey(final String identifier) { + try { + final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); + final Path ecWalletFile = createECWalletFile(ecKeyPair, identifier); + LOG.debug("Created proxy EC wallet file {} for identifier: {}", ecWalletFile, identifier); + return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair)), true); + } catch (final GeneralSecurityException e) { + throw new RuntimeException(e); + } } public ArtifactSigner generateBLSProxyKey(final String identifier) throws UncheckedIOException { @@ -101,13 +111,19 @@ private Path createECWalletFile(final ECKeyPair ecKeyPair, final String identifi final Path keystoreDir = createSubDirectories( commitBoostParameters.getProxyKeystoresPath(), identifier, KeyType.SECP256K1); - final String fileName; + final ECPublicKey ecPublicKey = + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); + final String compressedPubHex = + EthPublicKeyUtils.getEncoded(ecPublicKey, true, false).toHexString(); + + final Path keystoreFile = keystoreDir.resolve(compressedPubHex + ".json"); try { - fileName = WalletUtils.generateWalletFile(password, ecKeyPair, keystoreDir.toFile(), true); + final WalletFile walletFile = Wallet.createStandard(password, ecKeyPair); + JSON_MAPPER.writeValue(keystoreFile.toFile(), walletFile); + return keystoreFile; } catch (final CipherException | IOException e) { throw new RuntimeException(e); } - return keystoreDir.resolve(fileName); } private static String readFile(final Path file) throws UncheckedIOException { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 9f21f7cdf..6f59e1c86 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -50,7 +50,8 @@ public Bytes computeSigningRoot( proxyKeyMessageToSign = new BlsProxyKeySchema().create(delegator, proxy); } else { final ECPublicKey proxy = - EthPublicKeyUtils.createPublicKey(Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); + EthPublicKeyUtils.bytesToECPublicKey( + Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); proxyKeyMessageToSign = new SECPProxyKeySchema().create(delegator, proxy); } return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java index ec31be69c..9d92c12c4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java @@ -28,7 +28,7 @@ public class SszSECPPublicKey extends SszByteVectorImpl { public SszSECPPublicKey(final Bytes publicKeyBytes) { super(SszSECPPublicKeySchema.INSTANCE, publicKeyBytes); - this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.createPublicKey(publicKeyBytes)); + this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes)); } public SszSECPPublicKey(final ECPublicKey publicKey) { @@ -38,7 +38,7 @@ public SszSECPPublicKey(final ECPublicKey publicKey) { SszSECPPublicKey(final TreeNode backingNode) { super(SszSECPPublicKeySchema.INSTANCE, backingNode); - this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.createPublicKey(getBytes())); + this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.bytesToECPublicKey(getBytes())); } public ECPublicKey getECPublicKey() { diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java new file mode 100644 index 000000000..b7c20d73c --- /dev/null +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java @@ -0,0 +1,103 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.web3signer.signing.ArtifactSigner; +import tech.pegasys.web3signer.signing.KeyType; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class ProxyKeyGeneratorTest { + @TempDir private Path commitBoostKeystoresPath; + + @TempDir private Path commitBoostPasswordDir; + + private ProxyKeyGenerator proxyKeyGenerator; + + @BeforeEach + void init() { + final CommitBoostParameters commitBoostParameters = + new TestCommitBoostParameters(commitBoostKeystoresPath, commitBoostPasswordDir); + proxyKeyGenerator = new ProxyKeyGenerator(commitBoostParameters); + } + + @Test + void generateBLSProxyKey() { + final ArtifactSigner artifactSigner = proxyKeyGenerator.generateBLSProxyKey("pubkey"); + assertThat( + commitBoostKeystoresPath + .resolve("pubkey") + .resolve(KeyType.BLS.name()) + .resolve(artifactSigner.getIdentifier() + ".json")) + .exists(); + } + + @Test + void generateECProxyKey() throws IOException { + final ArtifactSigner artifactSigner = proxyKeyGenerator.generateECProxyKey("pubkey"); + assertThat( + commitBoostKeystoresPath + .resolve("pubkey") + .resolve(KeyType.SECP256K1.name()) + .resolve(artifactSigner.getIdentifier() + ".json")) + .exists(); + } + + private static class TestCommitBoostParameters implements CommitBoostParameters { + private final Path keystorePath; + private final Path passwordFile; + + public TestCommitBoostParameters(final Path keystorePath, final Path passwordDir) { + this.keystorePath = keystorePath; + // create password file in passwordDir + this.passwordFile = passwordDir.resolve("password.txt"); + // write text to password file + try { + Files.writeString(passwordFile, "password"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Path getProxyKeystoresPath() { + return keystorePath; + } + + @Override + public Path getProxyKeystoresPasswordFile() { + return passwordFile; + } + + @Override + public Bytes32 getGenesisValidatorsRoot() { + return Bytes32.ZERO; + } + } +} diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java index 06eda8bbe..59ddc1016 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java @@ -109,7 +109,7 @@ public void signatureHasTheExpectedFormat() { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); final ECPublicKey key = - EthPublicKeyUtils.createPublicKeyFromBigInt(cs.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); final BigInteger v = BigInteger.ONE; @@ -171,7 +171,7 @@ private String executeEthSignTransaction(final JsonObject params) { final Credentials cs = Credentials.create("0x1618fc3e47aec7e70451256e033b9edb67f4c469258d8e2fbb105552f141ae41"); final ECPublicKey key = - EthPublicKeyUtils.createPublicKeyFromBigInt(cs.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.bigIntegerToECPublicKey(cs.getEcKeyPair().getPublicKey()); final String addr = Keys.getAddress(EthPublicKeyUtils.toHexString(key)); doAnswer( diff --git a/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java b/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java index 7f639069f..344299f16 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/util/PublicKeyUtils.java @@ -22,6 +22,6 @@ public class PublicKeyUtils { public static ECPublicKey createKeyFrom(final String hexString) { Bytes bytes = Bytes.fromHexString(hexString, 64); - return EthPublicKeyUtils.createPublicKey(bytes); + return EthPublicKeyUtils.bytesToECPublicKey(bytes); } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 6bbbc6fdf..69a3b4828 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -84,7 +84,13 @@ public static KeyPair createSecp256k1KeyPair(final SecureRandom random) return keyPairGenerator.generateKeyPair(); } - public static ECPublicKey createPublicKey(final Bytes value) { + /** + * Convert a public key in bytes format to an ECPublicKey. + * + * @param value The public key in bytes format + * @return The ECPublicKey + */ + public static ECPublicKey bytesToECPublicKey(final Bytes value) { if (value.size() != 33 && value.size() != 65 && value.size() != 64) { throw new IllegalArgumentException( "Invalid public key length. Expected 33, 64, or 65 bytes."); @@ -101,10 +107,10 @@ public static ECPublicKey createPublicKey(final Bytes value) { point = SECP256K1_DOMAIN.getCurve().decodePoint(value.toArrayUnsafe()); } - return createPublicKeyFromPoint(point); + return bcECPointToECPublicKey(point); } - private static ECPublicKey createPublicKeyFromPoint(final ECPoint point) { + private static ECPublicKey bcECPointToECPublicKey(final ECPoint point) { try { // Convert Bouncy Castle ECPoint to Java ECPoint final java.security.spec.ECPoint ecPoint = @@ -128,7 +134,7 @@ private static ECPublicKey createPublicKeyFromPoint(final ECPoint point) { * @return The created ECPublicKey * @throws IllegalArgumentException if the input is invalid */ - public static ECPublicKey createPublicKeyFromBigInt(final BigInteger publicKeyValue) { + public static ECPublicKey bigIntegerToECPublicKey(final BigInteger publicKeyValue) { if (publicKeyValue == null) { throw new IllegalArgumentException("Public key value cannot be null"); } @@ -151,7 +157,7 @@ public static ECPublicKey createPublicKeyFromBigInt(final BigInteger publicKeyVa System.arraycopy(publicKeyBytes, 0, fullPublicKeyBytes, 1, 64); // Use the existing createPublicKey method - return createPublicKey(Bytes.wrap(fullPublicKeyBytes)); + return bytesToECPublicKey(Bytes.wrap(fullPublicKeyBytes)); } /** diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java index 83e9edfbd..18a9ab749 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/azure/AzureKeyVaultSigner.java @@ -51,7 +51,7 @@ public class AzureKeyVaultSigner implements Signer { final AzureKeyVault azureKeyVault, final AzureHttpClientFactory azureHttpClientFactory) { this.config = config; - this.publicKey = EthPublicKeyUtils.createPublicKey(publicKey); + this.publicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKey); this.needsToHash = needsToHash; this.signingAlgo = useDeprecatedSignatureAlgorithm diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java index 9cf5d5670..b9f87275c 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/filebased/CredentialSigner.java @@ -32,7 +32,7 @@ public class CredentialSigner implements Signer { public CredentialSigner(final Credentials credentials, final boolean needToHash) { this.credentials = credentials; this.publicKey = - EthPublicKeyUtils.createPublicKeyFromBigInt(credentials.getEcKeyPair().getPublicKey()); + EthPublicKeyUtils.bigIntegerToECPublicKey(credentials.getEcKeyPair().getPublicKey()); this.needToHash = needToHash; } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 7c397e82f..c2023be62 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -236,7 +236,7 @@ private static String[] getCompressedSecpPublicKeysArray(final List e .map( keyPair -> EthPublicKeyUtils.getEncoded( - EthPublicKeyUtils.createPublicKeyFromBigInt(keyPair.getPublicKey()), + EthPublicKeyUtils.bigIntegerToECPublicKey(keyPair.getPublicKey()), true, false) .toHexString()) diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index db8a1ccaf..054344d75 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -51,7 +51,7 @@ class EthPublicKeyUtilsTest { @Test public void createsPublicKeyFromBytes() { final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(expectedPublicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(expectedPublicKeyBytes); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); verifyPublicKey(ecPublicKey, expectedPublicKeyBytes, expectedEcPoint); @@ -60,7 +60,7 @@ public void createsPublicKeyFromBytes() { @Test public void createsPublicKeyFromBigInteger() { final BigInteger publicKey = Numeric.toBigInt(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKeyFromBigInt(publicKey); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bigIntegerToECPublicKey(publicKey); final Bytes expectedPublicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPoint expectedEcPoint = createEcPoint(expectedPublicKeyBytes); @@ -86,13 +86,14 @@ private static Stream validPublicKeys() { @ParameterizedTest @MethodSource("validPublicKeys") void acceptsValidPublicKeySizes(final Bytes publicKey) { - assertThatCode(() -> EthPublicKeyUtils.createPublicKey(publicKey)).doesNotThrowAnyException(); + assertThatCode(() -> EthPublicKeyUtils.bytesToECPublicKey(publicKey)) + .doesNotThrowAnyException(); } @ParameterizedTest @ValueSource(ints = {0, 32, 34, 63, 66}) void throwsIllegalArgumentExceptionForInvalidPublicKeySize(final int size) { - assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(Bytes.random(size))) + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(Bytes.random(size))) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Invalid public key length. Expected 33, 64, or 65 bytes."); } @@ -100,7 +101,7 @@ void throwsIllegalArgumentExceptionForInvalidPublicKeySize(final int size) { @Test void throwsIllegalArgumentExceptionForInvalid33ByteKey() { Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(32)); - assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidCompressedKey)) + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidCompressedKey)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Incorrect length for infinity encoding"); } @@ -108,7 +109,7 @@ void throwsIllegalArgumentExceptionForInvalid33ByteKey() { @Test void throwsIllegalArgumentExceptionForInvalid65ByteKey() { Bytes invalidUncompressedKey = Bytes.concatenate(Bytes.of(0x00), Bytes.random(64)); - assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidUncompressedKey)) + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidUncompressedKey)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Incorrect length for infinity encoding"); } @@ -116,7 +117,7 @@ void throwsIllegalArgumentExceptionForInvalid65ByteKey() { @Test void throwsIllegalArgumentExceptionForInvalidCompressedKeyPrefix() { Bytes invalidCompressedKey = Bytes.concatenate(Bytes.of(0x04), Bytes.random(32)); - assertThatThrownBy(() -> EthPublicKeyUtils.createPublicKey(invalidCompressedKey)) + assertThatThrownBy(() -> EthPublicKeyUtils.bytesToECPublicKey(invalidCompressedKey)) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining("Incorrect length for uncompressed encoding"); } @@ -125,7 +126,7 @@ void throwsIllegalArgumentExceptionForInvalidCompressedKeyPrefix() { public void publicKeyIsConvertedToEthHexString() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); final String hexString = EthPublicKeyUtils.toHexString(ecPublicKey); assertThat(hexString).isEqualTo(PUBLIC_KEY); } @@ -134,7 +135,7 @@ public void publicKeyIsConvertedToEthHexString() { public void publicKeyIsConvertedToEthBytes() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); final Bytes bytes = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); assertThat(bytes).isEqualTo(publicKeyBytes); assertThat(bytes.size()).isEqualTo(64); @@ -144,7 +145,7 @@ public void publicKeyIsConvertedToEthBytes() { @Test public void encodePublicKey() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); - final ECPublicKey ecPublicKey = EthPublicKeyUtils.createPublicKey(publicKeyBytes); + final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); final Bytes uncompressedWithoutPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); final Bytes uncompressedWithPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, true); From 02562a0c8882d8eb518bf2d159e66efb7679ef49 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Tue, 29 Oct 2024 22:59:04 +1000 Subject: [PATCH 31/57] commit boost signing root generator unit test --- core/build.gradle | 1 + .../commitboost/SigningRootGeneratorTest.java | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java diff --git a/core/build.gradle b/core/build.gradle index edba2f10f..90d2b4663 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation 'tech.pegasys.teku.internal:bls-keystore' implementation 'tech.pegasys.teku.internal:serializer' implementation 'tech.pegasys.teku.internal:spec' + implementation 'tech.pegasys.teku.internal:networks' implementation 'tech.pegasys.teku.internal:unsigned' implementation 'tech.pegasys.teku.internal:jackson' runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl' diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java new file mode 100644 index 000000000..fb38c0570 --- /dev/null +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java @@ -0,0 +1,75 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat; + +import tech.pegasys.teku.networks.Eth2NetworkConfiguration; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; +import tech.pegasys.teku.spec.networks.Eth2Network; + +import java.io.IOException; +import java.util.Optional; + +import org.apache.tuweni.bytes.Bytes32; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class SigningRootGeneratorTest { + static Spec spec; + static Bytes32 genesisValidatorsRoot; + // constant generated and copied from Commit Boost Client + static final Bytes32 COMMIT_BOOST_COMPUTED_DOMAIN = + Bytes32.fromHexString("0x6d6d6f43b5303f2ad2010d699a76c8e62350947421a3e4a979779642cfdb0f66"); + + @BeforeAll + static void initSpecAndGVR() { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + builder.applyNetworkDefaults(Eth2Network.MAINNET); + Eth2NetworkConfiguration eth2NetworkConfiguration = builder.build(); + spec = eth2NetworkConfiguration.getSpec(); + + genesisValidatorsRoot = + eth2NetworkConfiguration + .getNetworkBoostrapConfig() + .getGenesisState() + .flatMap( + state -> { + try { + return Optional.of( + ChainDataLoader.loadState(spec, state).getGenesisValidatorsRoot()); + } catch (IOException e) { + return Optional.empty(); + } + }) + .orElseThrow(() -> new RuntimeException("Genesis state for MAINNET cannot be loaded")); + } + + @Test + void validComputedDomainForMAINNET() { + final SigningRootGenerator signingRootGenerator = + new SigningRootGenerator(spec, genesisValidatorsRoot); + assertThat(signingRootGenerator.getDomain()).isEqualTo(COMMIT_BOOST_COMPUTED_DOMAIN); + } + + @Test + void computeSigningRootForBLSProxyKey() { + // TODO: Implement this test + } + + @Test + void computeSigningRootforSECPProxyKey() { + // TODO: Implement this test + } +} From 40d2c27dd79ae14ececdca1a458af4584a58515c Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 00:04:17 +1000 Subject: [PATCH 32/57] unit test for signing root generator --- .../CommitBoostGenerateProxyKeyHandler.java | 37 ++++++++----------- .../commitboost/SigningRootGeneratorTest.java | 28 +++++++++++++- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index bc3580967..d38f5c513 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -33,6 +33,7 @@ import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes; public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); @@ -85,33 +86,25 @@ public void handle(final RoutingContext context) { }; // Add generated proxy key to DefaultArtifactSignerProvider signerForIdentifier.getSignerProvider().addProxySigner(artifactSigner, identifier).get(); - } catch (final Exception e) { - context.fail(INTERNAL_ERROR, e); - return; - } - final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); - final Optional optionalSig = - signerForIdentifier.sign( - identifier, - signingRootGenerator.computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme())); - if (optionalSig.isEmpty()) { - context.fail(NOT_FOUND); - return; - } + final ProxyKeyMessage proxyKeyMessage = + new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); + final Bytes signingRoot = + signingRootGenerator.computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme()); + final Optional optionalSig = signerForIdentifier.sign(identifier, signingRoot); + if (optionalSig.isEmpty()) { + context.fail(NOT_FOUND); + return; + } - final GenerateProxyKeyResponse generateProxyKeyResponse = - new GenerateProxyKeyResponse(proxyKeyMessage, optionalSig.get()); + final GenerateProxyKeyResponse generateProxyKeyResponse = + new GenerateProxyKeyResponse(proxyKeyMessage, optionalSig.get()); - // Encode and send response - try { + // Encode and send response final String jsonEncoded = JSON_MAPPER.writeValueAsString(generateProxyKeyResponse); context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); - } catch (final JsonProcessingException e) { - // this is not meant to happen - LOG.error("Failed to encode GenerateProxyKeyResponse to JSON", e); - context.fail(INTERNAL_ERROR); + } catch (final Exception e) { + context.fail(INTERNAL_ERROR, e); } } } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java index fb38c0570..dbfda5e03 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java @@ -14,14 +14,20 @@ import static org.assertj.core.api.Assertions.assertThat; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.bls.BLSSecretKey; import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import java.io.IOException; import java.util.Optional; +import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -29,9 +35,12 @@ class SigningRootGeneratorTest { static Spec spec; static Bytes32 genesisValidatorsRoot; - // constant generated and copied from Commit Boost Client static final Bytes32 COMMIT_BOOST_COMPUTED_DOMAIN = Bytes32.fromHexString("0x6d6d6f43b5303f2ad2010d699a76c8e62350947421a3e4a979779642cfdb0f66"); + private static final String BLS_PRIVATE_KEY_1 = + "3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35"; + private static final String BLS_PRIVATE_KEY_2 = + "32ae313afff2daa2ef7005a7f834bdf291855608fe82c24d30be6ac2017093a8"; @BeforeAll static void initSpecAndGVR() { @@ -65,7 +74,22 @@ void validComputedDomainForMAINNET() { @Test void computeSigningRootForBLSProxyKey() { - // TODO: Implement this test + final SigningRootGenerator signingRootGenerator = + new SigningRootGenerator(spec, genesisValidatorsRoot); + final BLSPublicKey delegator = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))) + .getPublicKey(); + final BLSPublicKey proxy = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_2))) + .getPublicKey(); + final ProxyKeyMessage proxyKeyMessage = + new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); + final Bytes signingRoot = + signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.BLS); + assertThat(signingRoot) + .isEqualTo( + Bytes.fromHexString( + "0x148a095bf06bf227190b95dcc5c269e7d054d17c11d2a30499e0a41d2e200a05")); } @Test From 6611ecd1471108b4d83e14eea98922ead889d7b7 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 10:50:47 +1000 Subject: [PATCH 33/57] Use GVR as ZERO. Update Signing root generator tests --- .../commandline/PicoCliNetworkOverrides.java | 14 +++ .../config/PicoCommitBoostApiParameters.java | 53 +++-------- .../subcommands/Eth2SubCommand.java | 1 - .../commitboost/ProxyKeyGeneratorTest.java | 6 -- .../commitboost/SigningRootGeneratorTest.java | 94 +++++++++++-------- .../signing/config/CommitBoostParameters.java | 7 +- .../DefaultArtifactSignerProviderTest.java | 6 -- 7 files changed, 88 insertions(+), 93 deletions(-) diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java index e9a48cd76..f1e49851b 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/PicoCliNetworkOverrides.java @@ -16,6 +16,7 @@ import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.web3signer.commandline.subcommands.Eth2SubCommand; +import org.apache.commons.lang3.StringUtils; import picocli.CommandLine; /** Mixin class to hold network overrides for the PicoCLI parser. */ @@ -74,6 +75,16 @@ public class PicoCliNetworkOverrides { arity = "1") private String trustedSetup = null; // Depends on network configuration + @CommandLine.Option( + names = {"--Xgenesis-state"}, + paramLabel = "", + hidden = true, + description = + "Override the genesis state. This value should be a file or URL pointing to an SSZ-encoded finalized checkpoint " + + "state.", + arity = "1") + private String genesisState; + public void applyOverrides(final Eth2NetworkConfiguration.Builder builder) { if (altairForkEpoch != null) { builder.altairForkEpoch(altairForkEpoch); @@ -93,5 +104,8 @@ public void applyOverrides(final Eth2NetworkConfiguration.Builder builder) { if (trustedSetup != null) { builder.trustedSetup(trustedSetup); } + if (StringUtils.isNotBlank(genesisState)) { + builder.customGenesisState(genesisState); + } } } diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java index 6d25d5c5b..b78703658 100644 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java +++ b/commandline/src/main/java/tech/pegasys/web3signer/commandline/config/PicoCommitBoostApiParameters.java @@ -21,7 +21,6 @@ import java.nio.file.Path; -import org.apache.commons.lang3.StringUtils; import org.apache.tuweni.bytes.Bytes32; import picocli.CommandLine; import picocli.CommandLine.Model.CommandSpec; @@ -30,6 +29,11 @@ import picocli.CommandLine.Spec; public class PicoCommitBoostApiParameters implements CommitBoostParameters { + // commit boost client defaults gvr to ZERO for its domain calculations. Set this to `false` to + // use the actual network's gvr value + private static final boolean USE_ZERO_GENESIS_VALIDATORS_ROOT = true; + private Bytes32 genesisValidatorsRoot = Bytes32.ZERO; + @Spec private CommandSpec commandSpec; // injected by picocli @CommandLine.Option( @@ -53,19 +57,6 @@ public class PicoCommitBoostApiParameters implements CommitBoostParameters { paramLabel = PATH_FORMAT_HELP) private Path proxyKeystoresPasswordFile; - // note: In the future, this can be moved to PicoCliNetworkOverrides if we want to determine gvr - // for slashing protection etc. in advance before getting the first signing call from CL. - @Option( - names = {"--genesis-state"}, - paramLabel = "", - description = - "The genesis state. This value should be a file or URL pointing to an SSZ-encoded finalized checkpoint " - + "state. It is used to determine the value of genesis validators root for Commit Boost API.", - arity = "1") - private String genesisState; - - private Bytes32 genesisValidatorsRoot; - @Override public boolean isEnabled() { return isCommitBoostApiEnabled; @@ -83,23 +74,9 @@ public Path getProxyKeystoresPasswordFile() { @Override public Bytes32 getGenesisValidatorsRoot() { - if (genesisValidatorsRoot == null) { - throw new IllegalStateException("Genesis validators root not set"); - } return genesisValidatorsRoot; } - /** - * Apply genesis state overrides to the network configuration only if commit boost API is enabled. - * - * @param builder The network configuration builder to apply overrides to. - */ - public void applyOverrides(final Eth2NetworkConfiguration.Builder builder) { - if (isCommitBoostApiEnabled && StringUtils.isNotBlank(genesisState)) { - builder.customGenesisState(genesisState); - } - } - /** * Validate the parameters for the commit boost API and initialize parameters which will be used * during run operation. @@ -127,20 +104,20 @@ public void validateParameters(final Eth2NetworkConfiguration eth2NetworkConfig) /** Load genesis state and obtain genesis validators root. */ private void loadGenesisValidatorsRoot(final Eth2NetworkConfiguration eth2NetworkConfig) { - final String parameterExceptionMessage = - "Unable to load genesis state to determine genesis validators root. Please provide custom genesis state using --genesis-state"; - final String genesisState = - eth2NetworkConfig - .getNetworkBoostrapConfig() - .getGenesisState() - .orElseThrow( - () -> new ParameterException(commandSpec.commandLine(), parameterExceptionMessage)); + if (USE_ZERO_GENESIS_VALIDATORS_ROOT) { + return; + } try { + final String genesisState = + eth2NetworkConfig.getNetworkBoostrapConfig().getGenesisState().orElseThrow(); + final BeaconState beaconState = ChainDataLoader.loadState(eth2NetworkConfig.getSpec(), genesisState); - genesisValidatorsRoot = beaconState.getGenesisValidatorsRoot(); + this.genesisValidatorsRoot = beaconState.getGenesisValidatorsRoot(); } catch (final Exception e) { - throw new ParameterException(commandSpec.commandLine(), parameterExceptionMessage); + throw new ParameterException( + commandSpec.commandLine(), + "Unable to load genesis state to determine genesis validators root. Please provide custom genesis state using --Xgenesis-state"); } } } 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 438f4647b..62b9178c5 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 @@ -193,7 +193,6 @@ private Eth2NetworkConfiguration.Builder createEth2NetworkConfigBuilder() { final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); builder.applyNetworkDefaults(network); networkOverrides.applyOverrides(builder); // custom fork epochs - commitBoostApiParameters.applyOverrides(builder); // genesis state return builder; } catch (final IllegalArgumentException e) { throw new ParameterException( diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java index b7c20d73c..a6bacdbb0 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGeneratorTest.java @@ -23,7 +23,6 @@ import java.nio.file.Files; import java.nio.file.Path; -import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -94,10 +93,5 @@ public Path getProxyKeystoresPath() { public Path getProxyKeystoresPasswordFile() { return passwordFile; } - - @Override - public Bytes32 getGenesisValidatorsRoot() { - return Bytes32.ZERO; - } } } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java index dbfda5e03..ebbf07ae8 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java @@ -19,63 +19,72 @@ import tech.pegasys.teku.bls.BLSSecretKey; import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.util.ChainDataLoader; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; -import java.io.IOException; -import java.util.Optional; +import java.util.HashMap; +import java.util.Map; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; class SigningRootGeneratorTest { - static Spec spec; - static Bytes32 genesisValidatorsRoot; - static final Bytes32 COMMIT_BOOST_COMPUTED_DOMAIN = - Bytes32.fromHexString("0x6d6d6f43b5303f2ad2010d699a76c8e62350947421a3e4a979779642cfdb0f66"); + private static final Bytes32 GVR = Bytes32.ZERO; + private static final Map DOMAIN_MAP = new HashMap<>(); + private static final Map BLS_PROXY_ROOT_MAP = new HashMap<>(); private static final String BLS_PRIVATE_KEY_1 = "3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35"; private static final String BLS_PRIVATE_KEY_2 = "32ae313afff2daa2ef7005a7f834bdf291855608fe82c24d30be6ac2017093a8"; @BeforeAll - static void initSpecAndGVR() { - final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); - builder.applyNetworkDefaults(Eth2Network.MAINNET); - Eth2NetworkConfiguration eth2NetworkConfiguration = builder.build(); - spec = eth2NetworkConfiguration.getSpec(); - - genesisValidatorsRoot = - eth2NetworkConfiguration - .getNetworkBoostrapConfig() - .getGenesisState() - .flatMap( - state -> { - try { - return Optional.of( - ChainDataLoader.loadState(spec, state).getGenesisValidatorsRoot()); - } catch (IOException e) { - return Optional.empty(); - } - }) - .orElseThrow(() -> new RuntimeException("Genesis state for MAINNET cannot be loaded")); + static void initExpectedSigningRoots() { + // precalculated Domain values from Commit Boost client implementation + DOMAIN_MAP.put( + Eth2Network.MAINNET, + Bytes32.fromHexString( + "0x6d6d6f43f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a9")); + DOMAIN_MAP.put( + Eth2Network.HOLESKY, + Bytes32.fromHexString( + "0x6d6d6f435b83a23759c560b2d0c64576e1dcfc34ea94c4988f3e0d9f77f05387")); + DOMAIN_MAP.put( + Eth2Network.SEPOLIA, + Bytes32.fromHexString( + "0x6d6d6f43d3010778cd08ee514b08fe67b6c503b510987a4ce43f42306d97c67c")); + // precalculated Proxy Message Signing Root values from Commit Boost client implementation + BLS_PROXY_ROOT_MAP.put( + Eth2Network.MAINNET, + Bytes32.fromHexString( + "0x36700803956402c24e232e5da8d7dda12796ba96e49177f37daab87dd852f0cd")); + BLS_PROXY_ROOT_MAP.put( + Eth2Network.HOLESKY, + Bytes32.fromHexString( + "0xdb1b20106a8955ddb47eb2c8c2fe602af8801e61f682f068fc968c65644e45b6")); + BLS_PROXY_ROOT_MAP.put( + Eth2Network.SEPOLIA, + Bytes32.fromHexString( + "0x99615a149344fc1beffc2085ae98b676bff384b92b45dd28bc1f62127c41505e")); } - @Test - void validComputedDomainForMAINNET() { - final SigningRootGenerator signingRootGenerator = - new SigningRootGenerator(spec, genesisValidatorsRoot); - assertThat(signingRootGenerator.getDomain()).isEqualTo(COMMIT_BOOST_COMPUTED_DOMAIN); + @ParameterizedTest + @EnumSource(names = {"MAINNET", "HOLESKY", "SEPOLIA"}) + void validComputedDomain(final Eth2Network network) { + final Spec spec = getSpec(network); + final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); + assertThat(signingRootGenerator.getDomain()).isEqualTo(DOMAIN_MAP.get(network)); } - @Test - void computeSigningRootForBLSProxyKey() { - final SigningRootGenerator signingRootGenerator = - new SigningRootGenerator(spec, genesisValidatorsRoot); + @ParameterizedTest + @EnumSource(names = {"MAINNET", "HOLESKY", "SEPOLIA"}) + void computeSigningRootForBLSProxyKey(final Eth2Network network) { + final Spec spec = getSpec(network); + final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); final BLSPublicKey delegator = new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))) .getPublicKey(); @@ -86,14 +95,19 @@ void computeSigningRootForBLSProxyKey() { new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); final Bytes signingRoot = signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.BLS); - assertThat(signingRoot) - .isEqualTo( - Bytes.fromHexString( - "0x148a095bf06bf227190b95dcc5c269e7d054d17c11d2a30499e0a41d2e200a05")); + // the expected value is calculated using the Commit Boost client implementation + assertThat(signingRoot).isEqualTo(BLS_PROXY_ROOT_MAP.get(network)); } @Test void computeSigningRootforSECPProxyKey() { // TODO: Implement this test } + + private static Spec getSpec(final Eth2Network network) { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + builder.applyNetworkDefaults(network); + Eth2NetworkConfiguration eth2NetworkConfiguration = builder.build(); + return eth2NetworkConfiguration.getSpec(); + } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java index e0e6dd4ca..e72883fee 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/CommitBoostParameters.java @@ -40,9 +40,12 @@ public interface CommitBoostParameters { Path getProxyKeystoresPasswordFile(); /** - * The Genesis Validators Root for the network used by the commit boost signing operations. + * The Genesis Validators Root for the network used by the commit boost signing operations. The + * Commit Boost Client implementation uses ZERO as the default value. * * @return Genesis Validators Root as Bytes32 */ - Bytes32 getGenesisValidatorsRoot(); + default Bytes32 getGenesisValidatorsRoot() { + return Bytes32.ZERO; + } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index c2023be62..cc316b6d6 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -36,7 +36,6 @@ import java.util.Optional; import java.util.stream.IntStream; -import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -274,10 +273,5 @@ public Path getProxyKeystoresPath() { public Path getProxyKeystoresPasswordFile() { return passwordFile; } - - @Override - public Bytes32 getGenesisValidatorsRoot() { - return Bytes32.ZERO; - } } } From 41e781b8b5068b2e3b7b6c49b9caa58549439860 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 16:13:12 +1000 Subject: [PATCH 34/57] Update Signing Root Generator Test to verify bls signature --- .../commitboost/SigningRootGeneratorTest.java | 75 +++++++++++++++++-- .../signing/secp256k1/EthPublicKeyUtils.java | 5 +- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java index ebbf07ae8..503bae855 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java @@ -22,25 +22,37 @@ import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; +import tech.pegasys.web3signer.signing.BlsArtifactSignature; +import tech.pegasys.web3signer.signing.BlsArtifactSigner; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import java.security.interfaces.ECPublicKey; import java.util.HashMap; import java.util.Map; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.web3j.crypto.ECKeyPair; +import org.web3j.utils.Numeric; class SigningRootGeneratorTest { private static final Bytes32 GVR = Bytes32.ZERO; private static final Map DOMAIN_MAP = new HashMap<>(); private static final Map BLS_PROXY_ROOT_MAP = new HashMap<>(); + private static final Map SECP_PROXY_ROOT_MAP = new HashMap<>(); + + private static final Map BLS_PROXY_MESSAGE_SIGNATURE_MAP = new HashMap<>(); + private static final Map SECP_PROXY_MESSAGE_SIGNATURE_MAP = new HashMap<>(); + private static final String BLS_PRIVATE_KEY_1 = "3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35"; private static final String BLS_PRIVATE_KEY_2 = "32ae313afff2daa2ef7005a7f834bdf291855608fe82c24d30be6ac2017093a8"; + private static final String SECP_PROXY_PRIVATE_KEY = + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"; @BeforeAll static void initExpectedSigningRoots() { @@ -57,6 +69,7 @@ static void initExpectedSigningRoots() { Eth2Network.SEPOLIA, Bytes32.fromHexString( "0x6d6d6f43d3010778cd08ee514b08fe67b6c503b510987a4ce43f42306d97c67c")); + // precalculated Proxy Message Signing Root values from Commit Boost client implementation BLS_PROXY_ROOT_MAP.put( Eth2Network.MAINNET, @@ -70,6 +83,17 @@ static void initExpectedSigningRoots() { Eth2Network.SEPOLIA, Bytes32.fromHexString( "0x99615a149344fc1beffc2085ae98b676bff384b92b45dd28bc1f62127c41505e")); + + // precalculated Proxy Message Signature values from Commit Boost client implementation + BLS_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.MAINNET, + "0x99c739103b950777727a2e4da588de9017adbcae2ccb50ec1887ba0148a70055e55765441974e0a63f635267600b7bc00b2f616a8427f7d27ec7c1b6fd8520049294a9bbc07bea46aaeff59a254bf793fe57e0e67c41b457816839ff3da13f2e"); + BLS_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.HOLESKY, + "0xa13f26d6b77b17e385ce3411e7c0fba4b2bed81da5e14a50b419d3a2c2ea3c54a5b70c7c93b99bffa99781041b39335609c096639b7f742a0246204697ba67497ace0853a7f9d982356d78fdb99e98134302444596faec857570d9e5d578999c"); + BLS_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.SEPOLIA, + "0x8fd1736684a3eee3a4deea5785d41ddc7a96faf1dd4ff9778fb58db465a206571fff0ca9e55cd3654a28cfc0b0e065411633deb9cb8b9263f7189b73c013a61d6518a5aa2b40066a230a5cb1705bd9a80894badc7bfc65e3e2dd459e9fa9d7fc"); } @ParameterizedTest @@ -85,23 +109,60 @@ void validComputedDomain(final Eth2Network network) { void computeSigningRootForBLSProxyKey(final Eth2Network network) { final Spec spec = getSpec(network); final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); - final BLSPublicKey delegator = - new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))) - .getPublicKey(); + BLSKeyPair delegatorBLSKeyPair = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))); + final BLSPublicKey delegator = delegatorBLSKeyPair.getPublicKey(); final BLSPublicKey proxy = new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_2))) .getPublicKey(); + final ProxyKeyMessage proxyKeyMessage = new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); final Bytes signingRoot = signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.BLS); // the expected value is calculated using the Commit Boost client implementation assertThat(signingRoot).isEqualTo(BLS_PROXY_ROOT_MAP.get(network)); + + // verify BLS Signature matching Commit Boost client implementation as well + final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(delegatorBLSKeyPair, null); + BlsArtifactSignature blsArtifactSignature = artifactSigner.sign(signingRoot); + String signature = blsArtifactSignature.getSignatureData().toString(); + + assertThat(signature).isEqualTo(BLS_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } - @Test - void computeSigningRootforSECPProxyKey() { - // TODO: Implement this test + @ParameterizedTest + @EnumSource(names = {"MAINNET", "HOLESKY", "SEPOLIA"}) + void computeSigningRootforSECPProxyKey(final Eth2Network network) { + final Spec spec = getSpec(network); + final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); + // delegator BLS Key Pair + final BLSKeyPair delegatorBLSKeyPair = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))); + final BLSPublicKey delegator = delegatorBLSKeyPair.getPublicKey(); + + // proxy SECP Key Pair + final ECKeyPair ecKeyPair = + ECKeyPair.create(Numeric.toBigInt(Bytes.fromHexString(SECP_PROXY_PRIVATE_KEY).toArray())); + final ECPublicKey ecPublicKey = + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); + final Bytes proxy = EthPublicKeyUtils.getEncoded(ecPublicKey, true, false); + + final ProxyKeyMessage proxyKeyMessage = + new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); + + final Bytes signingRoot = + signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.ECDSA); + + // the expected value is calculated using the Commit Boost client implementation + assertThat(signingRoot).isEqualTo(SECP_PROXY_ROOT_MAP.get(network)); + + // verify BLS Signature matching Commit Boost client implementation as well + final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(delegatorBLSKeyPair, null); + BlsArtifactSignature blsArtifactSignature = artifactSigner.sign(signingRoot); + String signature = blsArtifactSignature.getSignatureData().toString(); + + assertThat(signature).isEqualTo(BLS_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } private static Spec getSpec(final Eth2Network network) { diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 69a3b4828..31669d662 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -189,8 +189,9 @@ public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { * * @param publicKey The public key to convert * @param compressed Whether to return the compressed form - * @param withPrefix Whether to include the prefix byte for uncompressed keys - * @return The encoded public key + * @param withPrefix Used when compressed flag is false. Set to true to include the prefix byte + * for uncompressed keys making the size 65 bytes. + * @return The encoded public key. */ public static Bytes getEncoded( final ECPublicKey publicKey, boolean compressed, boolean withPrefix) { From 300cc687686ad0598ccf420108313622f8ac5cb4 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 16:39:10 +1000 Subject: [PATCH 35/57] Update unit tests --- ...t.java => CommitBoostSigningRootTest.java} | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) rename core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/{SigningRootGeneratorTest.java => CommitBoostSigningRootTest.java} (70%) diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java similarity index 70% rename from core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java rename to core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java index 503bae855..0a06324ad 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGeneratorTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java @@ -38,7 +38,7 @@ import org.web3j.crypto.ECKeyPair; import org.web3j.utils.Numeric; -class SigningRootGeneratorTest { +class CommitBoostSigningRootTest { private static final Bytes32 GVR = Bytes32.ZERO; private static final Map DOMAIN_MAP = new HashMap<>(); private static final Map BLS_PROXY_ROOT_MAP = new HashMap<>(); @@ -47,16 +47,29 @@ class SigningRootGeneratorTest { private static final Map BLS_PROXY_MESSAGE_SIGNATURE_MAP = new HashMap<>(); private static final Map SECP_PROXY_MESSAGE_SIGNATURE_MAP = new HashMap<>(); - private static final String BLS_PRIVATE_KEY_1 = + private static final String BLS_DELEGATOR_PRIVATE_KEY = "3ee2224386c82ffea477e2adf28a2929f5c349165a4196158c7f3a2ecca40f35"; - private static final String BLS_PRIVATE_KEY_2 = + private static final String BLS_PROXY_PRIVATE_KEY = "32ae313afff2daa2ef7005a7f834bdf291855608fe82c24d30be6ac2017093a8"; private static final String SECP_PROXY_PRIVATE_KEY = "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"; + private static final BLSKeyPair DELEGATOR_KEY_PAIR = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_DELEGATOR_PRIVATE_KEY))); + private static final BLSPublicKey DELEGATOR_PUB_KEY = DELEGATOR_KEY_PAIR.getPublicKey(); + private static final BLSPublicKey BLS_PROXY_PUB_KEY = + new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PROXY_PRIVATE_KEY))) + .getPublicKey(); + private static final ECKeyPair SECP_PROXY_KEY_PAIR = + ECKeyPair.create(Numeric.toBigInt(Bytes.fromHexString(SECP_PROXY_PRIVATE_KEY).toArray())); + private static final ECPublicKey SECP_PROXY_EC_PUB_KEY = + EthPublicKeyUtils.bigIntegerToECPublicKey(SECP_PROXY_KEY_PAIR.getPublicKey()); + private static final Bytes SECP_PROXY_PUB_KEY_ENC = + EthPublicKeyUtils.getEncoded(SECP_PROXY_EC_PUB_KEY, true, false); + @BeforeAll static void initExpectedSigningRoots() { - // precalculated Domain values from Commit Boost client implementation + // precalculated values from Commit Boost client implementation DOMAIN_MAP.put( Eth2Network.MAINNET, Bytes32.fromHexString( @@ -70,7 +83,6 @@ static void initExpectedSigningRoots() { Bytes32.fromHexString( "0x6d6d6f43d3010778cd08ee514b08fe67b6c503b510987a4ce43f42306d97c67c")); - // precalculated Proxy Message Signing Root values from Commit Boost client implementation BLS_PROXY_ROOT_MAP.put( Eth2Network.MAINNET, Bytes32.fromHexString( @@ -84,6 +96,19 @@ static void initExpectedSigningRoots() { Bytes32.fromHexString( "0x99615a149344fc1beffc2085ae98b676bff384b92b45dd28bc1f62127c41505e")); + SECP_PROXY_ROOT_MAP.put( + Eth2Network.MAINNET, + Bytes32.fromHexString( + "0x419a4f6b748659b3ac4fc3534f3767fffe78127d210af0b2e1c1c8e7b345cf64")); + SECP_PROXY_ROOT_MAP.put( + Eth2Network.HOLESKY, + Bytes32.fromHexString( + "0xcc0cd2144f8b1c775eda156524e0a26ab794fdf39121ec902e51a4aff477fb74")); + SECP_PROXY_ROOT_MAP.put( + Eth2Network.SEPOLIA, + Bytes32.fromHexString( + "0xcc773c9f0ca058178f5b65b9c5fe9857c39e667f64f4b09a4e75731ac56fee41")); + // precalculated Proxy Message Signature values from Commit Boost client implementation BLS_PROXY_MESSAGE_SIGNATURE_MAP.put( Eth2Network.MAINNET, @@ -94,6 +119,16 @@ static void initExpectedSigningRoots() { BLS_PROXY_MESSAGE_SIGNATURE_MAP.put( Eth2Network.SEPOLIA, "0x8fd1736684a3eee3a4deea5785d41ddc7a96faf1dd4ff9778fb58db465a206571fff0ca9e55cd3654a28cfc0b0e065411633deb9cb8b9263f7189b73c013a61d6518a5aa2b40066a230a5cb1705bd9a80894badc7bfc65e3e2dd459e9fa9d7fc"); + + SECP_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.MAINNET, + "0x8cd715641bb61bca8ba50a5f7e5faf06da1aedb074d59b9fce0ab69e8840501975f5c0008de6625b7d343b5bd362e3220ef5be03c1b32842cddcd5073c3d25e22e9746144b8ff2361391af1b681520c111b5ea69f11097991cccb43b9b6fb0e9"); + SECP_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.HOLESKY, + "0x8f3c546da13ad082e2818b75b4662e4f28d38e193a5c4e24231233df454f26f15c8ab1fd4cc4772321ab7a4ac16acaf300a78c5fc1ac96c4009413ad7f9c6c5cd99cb9d7c92120177d828bd8f6e77b9ffb93c37f6f6b3cb264969fa4fea179d5"); + SECP_PROXY_MESSAGE_SIGNATURE_MAP.put( + Eth2Network.SEPOLIA, + "0x90000272c0a751852d28b953c9d30df31fd9eeb846fb3b575c8fdeee0325ee5dcc6f91bdf3d5d0f0814b707d088ab3af047977464cbe3b9eded66202c2ae70fbe478860cbcf4fc31d10a81aac7c682a6e422686a7cfa7cab272903f9cabf73bb"); } @ParameterizedTest @@ -109,24 +144,17 @@ void validComputedDomain(final Eth2Network network) { void computeSigningRootForBLSProxyKey(final Eth2Network network) { final Spec spec = getSpec(network); final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); - BLSKeyPair delegatorBLSKeyPair = - new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))); - final BLSPublicKey delegator = delegatorBLSKeyPair.getPublicKey(); - final BLSPublicKey proxy = - new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_2))) - .getPublicKey(); final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); + new ProxyKeyMessage(DELEGATOR_PUB_KEY.toHexString(), BLS_PROXY_PUB_KEY.toHexString()); final Bytes signingRoot = signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.BLS); - // the expected value is calculated using the Commit Boost client implementation + assertThat(signingRoot).isEqualTo(BLS_PROXY_ROOT_MAP.get(network)); // verify BLS Signature matching Commit Boost client implementation as well - final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(delegatorBLSKeyPair, null); - BlsArtifactSignature blsArtifactSignature = artifactSigner.sign(signingRoot); - String signature = blsArtifactSignature.getSignatureData().toString(); + final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(DELEGATOR_KEY_PAIR, null); + final String signature = artifactSigner.sign(signingRoot).getSignatureData().toString(); assertThat(signature).isEqualTo(BLS_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } @@ -136,33 +164,21 @@ void computeSigningRootForBLSProxyKey(final Eth2Network network) { void computeSigningRootforSECPProxyKey(final Eth2Network network) { final Spec spec = getSpec(network); final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); - // delegator BLS Key Pair - final BLSKeyPair delegatorBLSKeyPair = - new BLSKeyPair(BLSSecretKey.fromBytes(Bytes32.fromHexString(BLS_PRIVATE_KEY_1))); - final BLSPublicKey delegator = delegatorBLSKeyPair.getPublicKey(); - - // proxy SECP Key Pair - final ECKeyPair ecKeyPair = - ECKeyPair.create(Numeric.toBigInt(Bytes.fromHexString(SECP_PROXY_PRIVATE_KEY).toArray())); - final ECPublicKey ecPublicKey = - EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); - final Bytes proxy = EthPublicKeyUtils.getEncoded(ecPublicKey, true, false); final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(delegator.toHexString(), proxy.toHexString()); + new ProxyKeyMessage(DELEGATOR_PUB_KEY.toHexString(), SECP_PROXY_PUB_KEY_ENC.toHexString()); final Bytes signingRoot = signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.ECDSA); - // the expected value is calculated using the Commit Boost client implementation assertThat(signingRoot).isEqualTo(SECP_PROXY_ROOT_MAP.get(network)); // verify BLS Signature matching Commit Boost client implementation as well - final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(delegatorBLSKeyPair, null); + final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(DELEGATOR_KEY_PAIR, null); BlsArtifactSignature blsArtifactSignature = artifactSigner.sign(signingRoot); String signature = blsArtifactSignature.getSignatureData().toString(); - assertThat(signature).isEqualTo(BLS_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); + assertThat(signature).isEqualTo(SECP_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } private static Spec getSpec(final Eth2Network network) { From b196f63c4f4402ce9d2b0c877581af3befd50b44 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 17:22:33 +1000 Subject: [PATCH 36/57] Refactor ProxyDelegation names and getEncoded method --- .../CommitBoostGenerateProxyKeyHandler.java | 16 ++++++------- .../commitboost/ProxyKeyGenerator.java | 3 +-- .../commitboost/SigningRootGenerator.java | 24 ++++--------------- ...eyMessage.java => BLSProxyDelegation.java} | 14 ++++++----- ...ema.java => BLSProxyDelegationSchema.java} | 19 ++++++++------- ...yMessage.java => SECPProxyDelegation.java} | 16 +++++++------ ...ma.java => SECPProxyDelegationSchema.java} | 21 ++++++++++------ .../datastructure/SszSECPPublicKey.java | 8 +------ ...xyKeyMessage.java => ProxyDelegation.java} | 15 ++++++++++-- ...sponse.java => SignedProxyDelegation.java} | 4 ++-- .../CommitBoostSigningRootTest.java | 16 ++++++------- .../signing/EthSecpArtifactSigner.java | 2 +- .../signing/secp256k1/EthPublicKeyUtils.java | 22 +++++++---------- .../DefaultArtifactSignerProviderTest.java | 4 +--- .../secp256k1/EthPublicKeyUtilsTest.java | 18 +++++++------- 15 files changed, 98 insertions(+), 104 deletions(-) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/{BlsProxyKeyMessage.java => BLSProxyDelegation.java} (70%) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/{BlsProxyKeySchema.java => BLSProxyDelegationSchema.java} (62%) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/{SECPProxyKeyMessage.java => SECPProxyDelegation.java} (71%) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/{SECPProxyKeySchema.java => SECPProxyDelegationSchema.java} (60%) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/{ProxyKeyMessage.java => ProxyDelegation.java} (60%) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/{GenerateProxyKeyResponse.java => SignedProxyDelegation.java} (87%) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index d38f5c513..58f9ee1ba 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -19,8 +19,8 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyResponse; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignedProxyDelegation; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; @@ -87,21 +87,21 @@ public void handle(final RoutingContext context) { // Add generated proxy key to DefaultArtifactSignerProvider signerForIdentifier.getSignerProvider().addProxySigner(artifactSigner, identifier).get(); - final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(identifier, artifactSigner.getIdentifier()); + final ProxyDelegation proxyDelegation = + new ProxyDelegation(identifier, artifactSigner.getIdentifier()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyKeyMessage, proxyKeyBody.scheme()); + signingRootGenerator.computeSigningRoot(proxyDelegation, proxyKeyBody.scheme()); final Optional optionalSig = signerForIdentifier.sign(identifier, signingRoot); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); return; } - final GenerateProxyKeyResponse generateProxyKeyResponse = - new GenerateProxyKeyResponse(proxyKeyMessage, optionalSig.get()); + final SignedProxyDelegation signedProxyDelegation = + new SignedProxyDelegation(proxyDelegation, optionalSig.get()); // Encode and send response - final String jsonEncoded = JSON_MAPPER.writeValueAsString(generateProxyKeyResponse); + final String jsonEncoded = JSON_MAPPER.writeValueAsString(signedProxyDelegation); context.response().putHeader(CONTENT_TYPE, JSON_UTF_8).end(jsonEncoded); } catch (final Exception e) { context.fail(INTERNAL_ERROR, e); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index ca9bb35c2..4598eab86 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -113,8 +113,7 @@ private Path createECWalletFile(final ECKeyPair ecKeyPair, final String identifi commitBoostParameters.getProxyKeystoresPath(), identifier, KeyType.SECP256K1); final ECPublicKey ecPublicKey = EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()); - final String compressedPubHex = - EthPublicKeyUtils.getEncoded(ecPublicKey, true, false).toHexString(); + final String compressedPubHex = EthPublicKeyUtils.getEncoded(ecPublicKey, true).toHexString(); final Path keystoreFile = keystoreDir.resolve(compressedPubHex + ".json"); try { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 6f59e1c86..6dac4b768 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -12,18 +12,12 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.commitboost; -import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.ssz.Merkleizable; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BlsProxyKeySchema; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.SECPProxyKeySchema; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; -import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; - -import java.security.interfaces.ECPublicKey; import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; @@ -41,20 +35,10 @@ public SigningRootGenerator(final Spec eth2Spec, final Bytes32 genesisValidators } public Bytes computeSigningRoot( - final ProxyKeyMessage proxyKeyMessage, final ProxyKeySignatureScheme scheme) { + final ProxyDelegation proxyDelegation, final ProxyKeySignatureScheme scheme) { + final Merkleizable proxyDelegationMerkleizable = proxyDelegation.toMerkleizable(scheme); - final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyKeyMessage.blsPublicKey()); - final Merkleizable proxyKeyMessageToSign; - if (scheme == ProxyKeySignatureScheme.BLS) { - final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyKeyMessage.proxyPublicKey()); - proxyKeyMessageToSign = new BlsProxyKeySchema().create(delegator, proxy); - } else { - final ECPublicKey proxy = - EthPublicKeyUtils.bytesToECPublicKey( - Bytes.fromHexString(proxyKeyMessage.proxyPublicKey())); - proxyKeyMessageToSign = new SECPProxyKeySchema().create(delegator, proxy); - } - return Web3SignerSigningRootUtil.computeSigningRoot(proxyKeyMessageToSign, domain); + return Web3SignerSigningRootUtil.computeSigningRoot(proxyDelegationMerkleizable, domain); } @VisibleForTesting diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegation.java similarity index 70% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegation.java index 1ba1fdead..b0c65562f 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeyMessage.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegation.java @@ -17,19 +17,21 @@ import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -public class BlsProxyKeyMessage extends Container2 { +public class BLSProxyDelegation extends Container2 { - public BlsProxyKeyMessage( - final BlsProxyKeySchema schema, final BLSPublicKey delegator, final BLSPublicKey proxy) { + public BLSProxyDelegation( + final BLSProxyDelegationSchema schema, + final BLSPublicKey delegator, + final BLSPublicKey proxy) { super(schema, new SszPublicKey(delegator), new SszPublicKey(proxy)); } - BlsProxyKeyMessage(final BlsProxyKeySchema type, final TreeNode backingNode) { + BLSProxyDelegation(final BLSProxyDelegationSchema type, final TreeNode backingNode) { super(type, backingNode); } @Override - public BlsProxyKeySchema getSchema() { - return (BlsProxyKeySchema) super.getSchema(); + public BLSProxyDelegationSchema getSchema() { + return (BLSProxyDelegationSchema) super.getSchema(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegationSchema.java similarity index 62% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegationSchema.java index 5b42fc0e2..2a32777db 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BlsProxyKeySchema.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/BLSProxyDelegationSchema.java @@ -17,22 +17,25 @@ import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; -public class BlsProxyKeySchema - extends ContainerSchema2 { - public BlsProxyKeySchema() { +public class BLSProxyDelegationSchema + extends ContainerSchema2 { + public BLSProxyDelegationSchema() { super( - "BlsProxyKeyMessage", + "BLSProxyDelegationSchema", namedSchema("delegator", SszPublicKeySchema.INSTANCE), namedSchema("proxy", SszPublicKeySchema.INSTANCE)); } - public BlsProxyKeyMessage create(final BLSPublicKey delegator, final BLSPublicKey proxy) { - return new BlsProxyKeyMessage(this, delegator, proxy); + public BLSProxyDelegation create(final ProxyDelegation proxyDelegation) { + final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyDelegation.blsPublicKey()); + final BLSPublicKey proxy = BLSPublicKey.fromHexString(proxyDelegation.proxyPublicKey()); + return new BLSProxyDelegation(this, delegator, proxy); } @Override - public BlsProxyKeyMessage createFromBackingNode(TreeNode treeNode) { - return new BlsProxyKeyMessage(this, treeNode); + public BLSProxyDelegation createFromBackingNode(TreeNode treeNode) { + return new BLSProxyDelegation(this, treeNode); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegation.java similarity index 71% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegation.java index 8694a3878..199326c27 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeyMessage.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegation.java @@ -19,20 +19,22 @@ import java.security.interfaces.ECPublicKey; -public class SECPProxyKeyMessage - extends Container2 { +public class SECPProxyDelegation + extends Container2 { - public SECPProxyKeyMessage( - final SECPProxyKeySchema schema, final BLSPublicKey delegator, final ECPublicKey proxy) { + public SECPProxyDelegation( + final SECPProxyDelegationSchema schema, + final BLSPublicKey delegator, + final ECPublicKey proxy) { super(schema, new SszPublicKey(delegator), new SszSECPPublicKey(proxy)); } - SECPProxyKeyMessage(final SECPProxyKeySchema schema, final TreeNode backingNode) { + SECPProxyDelegation(final SECPProxyDelegationSchema schema, final TreeNode backingNode) { super(schema, backingNode); } @Override - public SECPProxyKeySchema getSchema() { - return (SECPProxyKeySchema) super.getSchema(); + public SECPProxyDelegationSchema getSchema() { + return (SECPProxyDelegationSchema) super.getSchema(); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java similarity index 60% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java index 65dcc31f8..d79f8f8a9 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyKeySchema.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java @@ -17,24 +17,31 @@ import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; import java.security.interfaces.ECPublicKey; -public class SECPProxyKeySchema - extends ContainerSchema2 { - public SECPProxyKeySchema() { +import org.apache.tuweni.bytes.Bytes; + +public class SECPProxyDelegationSchema + extends ContainerSchema2 { + public SECPProxyDelegationSchema() { super( "SECPProxyKeyMessage", namedSchema("delegator", SszPublicKeySchema.INSTANCE), namedSchema("proxy", SszSECPPublicKeySchema.INSTANCE)); } - public SECPProxyKeyMessage create(final BLSPublicKey delegator, final ECPublicKey proxy) { - return new SECPProxyKeyMessage(this, delegator, proxy); + public SECPProxyDelegation create(final ProxyDelegation proxyDelegation) { + final BLSPublicKey delegator = BLSPublicKey.fromHexString(proxyDelegation.blsPublicKey()); + final ECPublicKey proxy = + EthPublicKeyUtils.bytesToECPublicKey(Bytes.fromHexString(proxyDelegation.proxyPublicKey())); + return new SECPProxyDelegation(this, delegator, proxy); } @Override - public SECPProxyKeyMessage createFromBackingNode(final TreeNode treeNode) { - return new SECPProxyKeyMessage(this, treeNode); + public SECPProxyDelegation createFromBackingNode(final TreeNode treeNode) { + return new SECPProxyDelegation(this, treeNode); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java index 9d92c12c4..d23f606b2 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SszSECPPublicKey.java @@ -20,19 +20,13 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; -import org.apache.tuweni.bytes.Bytes; public class SszSECPPublicKey extends SszByteVectorImpl { private final Supplier publicKey; - public SszSECPPublicKey(final Bytes publicKeyBytes) { - super(SszSECPPublicKeySchema.INSTANCE, publicKeyBytes); - this.publicKey = Suppliers.memoize(() -> EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes)); - } - public SszSECPPublicKey(final ECPublicKey publicKey) { - super(SszSECPPublicKeySchema.INSTANCE, EthPublicKeyUtils.getEncoded(publicKey, true, true)); + super(SszSECPPublicKeySchema.INSTANCE, EthPublicKeyUtils.getEncoded(publicKey, true)); this.publicKey = () -> publicKey; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyDelegation.java similarity index 60% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyDelegation.java index 9851700ad..17deb1c60 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyKeyMessage.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/ProxyDelegation.java @@ -12,8 +12,19 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.commitboost.json; +import tech.pegasys.teku.infrastructure.ssz.Merkleizable; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.BLSProxyDelegationSchema; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.datastructure.SECPProxyDelegationSchema; + import com.fasterxml.jackson.annotation.JsonProperty; -public record ProxyKeyMessage( +public record ProxyDelegation( @JsonProperty(value = "delegator", required = true) String blsPublicKey, - @JsonProperty(value = "proxy", required = true) String proxyPublicKey) {} + @JsonProperty(value = "proxy", required = true) String proxyPublicKey) { + + public Merkleizable toMerkleizable(final ProxyKeySignatureScheme scheme) { + return scheme == ProxyKeySignatureScheme.BLS + ? new BLSProxyDelegationSchema().create(this) + : new SECPProxyDelegationSchema().create(this); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignedProxyDelegation.java similarity index 87% rename from core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java rename to core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignedProxyDelegation.java index 5cb518cd1..478d41efc 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/GenerateProxyKeyResponse.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignedProxyDelegation.java @@ -14,6 +14,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; -public record GenerateProxyKeyResponse( - @JsonProperty(value = "message") ProxyKeyMessage proxyKeyMessage, +public record SignedProxyDelegation( + @JsonProperty(value = "message") ProxyDelegation proxyDelegation, @JsonProperty(value = "signature") String signature) {} diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java index 0a06324ad..e2a3e313a 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java @@ -20,7 +20,7 @@ import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.networks.Eth2Network; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeyMessage; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.signing.BlsArtifactSignature; import tech.pegasys.web3signer.signing.BlsArtifactSigner; @@ -65,7 +65,7 @@ class CommitBoostSigningRootTest { private static final ECPublicKey SECP_PROXY_EC_PUB_KEY = EthPublicKeyUtils.bigIntegerToECPublicKey(SECP_PROXY_KEY_PAIR.getPublicKey()); private static final Bytes SECP_PROXY_PUB_KEY_ENC = - EthPublicKeyUtils.getEncoded(SECP_PROXY_EC_PUB_KEY, true, false); + EthPublicKeyUtils.getEncoded(SECP_PROXY_EC_PUB_KEY, true); @BeforeAll static void initExpectedSigningRoots() { @@ -145,10 +145,10 @@ void computeSigningRootForBLSProxyKey(final Eth2Network network) { final Spec spec = getSpec(network); final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); - final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(DELEGATOR_PUB_KEY.toHexString(), BLS_PROXY_PUB_KEY.toHexString()); + final ProxyDelegation proxyDelegation = + new ProxyDelegation(DELEGATOR_PUB_KEY.toHexString(), BLS_PROXY_PUB_KEY.toHexString()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.BLS); + signingRootGenerator.computeSigningRoot(proxyDelegation, ProxyKeySignatureScheme.BLS); assertThat(signingRoot).isEqualTo(BLS_PROXY_ROOT_MAP.get(network)); @@ -165,11 +165,11 @@ void computeSigningRootforSECPProxyKey(final Eth2Network network) { final Spec spec = getSpec(network); final SigningRootGenerator signingRootGenerator = new SigningRootGenerator(spec, GVR); - final ProxyKeyMessage proxyKeyMessage = - new ProxyKeyMessage(DELEGATOR_PUB_KEY.toHexString(), SECP_PROXY_PUB_KEY_ENC.toHexString()); + final ProxyDelegation proxyDelegation = + new ProxyDelegation(DELEGATOR_PUB_KEY.toHexString(), SECP_PROXY_PUB_KEY_ENC.toHexString()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyKeyMessage, ProxyKeySignatureScheme.ECDSA); + signingRootGenerator.computeSigningRoot(proxyDelegation, ProxyKeySignatureScheme.ECDSA); assertThat(signingRoot).isEqualTo(SECP_PROXY_ROOT_MAP.get(network)); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java index 51525861a..f36023730 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java @@ -37,7 +37,7 @@ public EthSecpArtifactSigner(final Signer signer, final boolean isCompressed) { @Override public String getIdentifier() { final String hexString = - EthPublicKeyUtils.getEncoded(signer.getPublicKey(), isCompressed, false).toHexString(); + EthPublicKeyUtils.getEncoded(signer.getPublicKey(), isCompressed).toHexString(); return IdentifierUtils.normaliseIdentifier(hexString); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 31669d662..6a143252f 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -167,7 +167,7 @@ public static ECPublicKey bigIntegerToECPublicKey(final BigInteger publicKeyValu * @return The public key as a hex string */ public static String toHexString(final ECPublicKey publicKey) { - return getEncoded(publicKey, false, false).toHexString(); + return getEncoded(publicKey, false).toHexString(); } /** @@ -178,7 +178,7 @@ public static String toHexString(final ECPublicKey publicKey) { */ public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { // Get the uncompressed public key without prefix (64 bytes) - final Bytes publicKeyBytes = EthPublicKeyUtils.getEncoded(publicKey, false, false); + final Bytes publicKeyBytes = EthPublicKeyUtils.getEncoded(publicKey, false); // Convert to BigInteger return new BigInteger(1, publicKeyBytes.toArrayUnsafe()); @@ -188,13 +188,11 @@ public static BigInteger ecPublicKeyToBigInteger(final ECPublicKey publicKey) { * Convert java ECPublicKey to Bytes. * * @param publicKey The public key to convert - * @param compressed Whether to return the compressed form - * @param withPrefix Used when compressed flag is false. Set to true to include the prefix byte - * for uncompressed keys making the size 65 bytes. + * @param compressed Whether to return the compressed form 33 bytes or the uncompressed form 64 + * bytes * @return The encoded public key. */ - public static Bytes getEncoded( - final ECPublicKey publicKey, boolean compressed, boolean withPrefix) { + public static Bytes getEncoded(final ECPublicKey publicKey, boolean compressed) { final ECPoint point; if (publicKey instanceof BCECPublicKey) { // If it's already a Bouncy Castle key, we can get the ECPoint directly @@ -206,12 +204,8 @@ public static Bytes getEncoded( point = BC_SECP256K1_SPEC.getCurve().createPoint(x, y); } - if (compressed) { - return Bytes.wrap(point.getEncoded(true)); - } else if (withPrefix) { - return Bytes.wrap(point.getEncoded(false)); - } else { - return Bytes.wrap(point.getEncoded(false), 1, 64); - } + return compressed + ? Bytes.wrap(point.getEncoded(true)) + : Bytes.wrap(point.getEncoded(false), 1, 64); } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index cc316b6d6..30466e6a5 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -235,9 +235,7 @@ private static String[] getCompressedSecpPublicKeysArray(final List e .map( keyPair -> EthPublicKeyUtils.getEncoded( - EthPublicKeyUtils.bigIntegerToECPublicKey(keyPair.getPublicKey()), - true, - false) + EthPublicKeyUtils.bigIntegerToECPublicKey(keyPair.getPublicKey()), true) .toHexString()) .toList() .toArray(String[]::new); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java index 054344d75..b007c9c40 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtilsTest.java @@ -68,7 +68,7 @@ public void createsPublicKeyFromBigInteger() { } private static Stream validPublicKeys() { - KeyPair keyPair = null; + final KeyPair keyPair; try { keyPair = EthPublicKeyUtils.createSecp256k1KeyPair(new SecureRandom()); } catch (GeneralSecurityException e) { @@ -76,11 +76,13 @@ private static Stream validPublicKeys() { } return Stream.of( // Compressed (33 bytes) - EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), true, true), + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), true), // Uncompressed without prefix (64 bytes) - EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false, false), + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false), // Uncompressed with prefix (65 bytes) - EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false, true)); + Bytes.concatenate( + Bytes.of(0x04), + EthPublicKeyUtils.getEncoded((ECPublicKey) keyPair.getPublic(), false))); } @ParameterizedTest @@ -136,7 +138,7 @@ public void publicKeyIsConvertedToEthBytes() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); - final Bytes bytes = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); + final Bytes bytes = EthPublicKeyUtils.getEncoded(ecPublicKey, false); assertThat(bytes).isEqualTo(publicKeyBytes); assertThat(bytes.size()).isEqualTo(64); assertThat(bytes.get(0)).isNotEqualTo(0x4); @@ -147,12 +149,10 @@ public void encodePublicKey() { final Bytes publicKeyBytes = Bytes.fromHexString(PUBLIC_KEY); final ECPublicKey ecPublicKey = EthPublicKeyUtils.bytesToECPublicKey(publicKeyBytes); - final Bytes uncompressedWithoutPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, false); - final Bytes uncompressedWithPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false, true); - final Bytes compressed = EthPublicKeyUtils.getEncoded(ecPublicKey, true, true); + final Bytes uncompressedWithoutPrefix = EthPublicKeyUtils.getEncoded(ecPublicKey, false); + final Bytes compressed = EthPublicKeyUtils.getEncoded(ecPublicKey, true); assertThat(uncompressedWithoutPrefix.size()).isEqualTo(64); - assertThat(uncompressedWithPrefix.size()).isEqualTo(65); assertThat(compressed.size()).isEqualTo(33); } From 0ab780adb01bb65b278c599b3c61f9c3b22211f4 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 20:43:52 +1000 Subject: [PATCH 37/57] Refactor Eth2SubCommand --- .../subcommands/Eth2SubCommand.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) 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 62b9178c5..ea4d2d8e8 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 @@ -164,9 +164,7 @@ private void logNetworkSpecInformation() { @Override protected void validateArgs() { - final Eth2NetworkConfiguration.Builder eth2NetworkConfigBuilder = - createEth2NetworkConfigBuilder(); - this.eth2NetworkConfig = createEth2NetworkConfig(eth2NetworkConfigBuilder); + this.eth2NetworkConfig = createEth2NetworkConfiguration(); if (slashingProtectionParameters.isEnabled() && slashingProtectionParameters.getDbUrl() == null) { @@ -188,23 +186,11 @@ protected void validateArgs() { commitBoostApiParameters.validateParameters(this.eth2NetworkConfig); } - private Eth2NetworkConfiguration.Builder createEth2NetworkConfigBuilder() { + private Eth2NetworkConfiguration createEth2NetworkConfiguration() { try { final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); builder.applyNetworkDefaults(network); networkOverrides.applyOverrides(builder); // custom fork epochs - return builder; - } catch (final IllegalArgumentException e) { - throw new ParameterException( - commandSpec.commandLine(), - "Failed to load network " + network + " due to " + e.getMessage(), - e); - } - } - - private Eth2NetworkConfiguration createEth2NetworkConfig( - final Eth2NetworkConfiguration.Builder builder) throws ParameterException { - try { return builder.build(); } catch (final IllegalArgumentException e) { throw new ParameterException( From 27c9188f106e36d41c9806159307ca434e942706 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 20:49:08 +1000 Subject: [PATCH 38/57] Delete Unused Bytes32Converter --- .../convertor/Bytes32Converter.java | 27 ------------------- 1 file changed, 27 deletions(-) delete mode 100644 commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java diff --git a/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java b/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java deleted file mode 100644 index 10c38d3b7..000000000 --- a/commandline/src/main/java/tech/pegasys/web3signer/commandline/convertor/Bytes32Converter.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.commandline.convertor; - -import org.apache.tuweni.bytes.Bytes32; -import picocli.CommandLine; - -public class Bytes32Converter implements CommandLine.ITypeConverter { - @Override - public Bytes32 convert(final String hexString) throws Exception { - try { - return Bytes32.fromHexString(hexString); - } catch (final Exception e) { - throw new CommandLine.TypeConversionException("Invalid hex string: " + hexString); - } - } -} From 6c0e3b1482ad68217acf7aa64268cfaebebba885 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 20:59:14 +1000 Subject: [PATCH 39/57] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fab48afe..52474c935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - 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) - Commit boost API - Get Public Keys. [#1031](https://github.com/Consensys/web3signer/pull/1031) +- Commit boost API - Generate Proxy Keys. [#1033](https://github.com/Consensys/web3signer/pull/1033) ### Bugs fixed - Override protobuf-java to 3.25.5 which is a transitive dependency from google-cloud-secretmanager. It fixes CVE-2024-7254. From 64b20d1ddc822176fc715b38337d80c291a05e17 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 30 Oct 2024 21:08:28 +1000 Subject: [PATCH 40/57] minor refactoring --- .../commitboost/datastructure/SECPProxyDelegationSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java index d79f8f8a9..4a7713213 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/datastructure/SECPProxyDelegationSchema.java @@ -28,7 +28,7 @@ public class SECPProxyDelegationSchema extends ContainerSchema2 { public SECPProxyDelegationSchema() { super( - "SECPProxyKeyMessage", + "SECPProxyDelegationSchema", namedSchema("delegator", SszPublicKeySchema.INSTANCE), namedSchema("proxy", SszSECPPublicKeySchema.INSTANCE)); } From 5a8a343bd1f864d1683cd118be3d51b66ae20817 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Fri, 1 Nov 2024 12:13:42 +1000 Subject: [PATCH 41/57] Commit Boost - Request Signature --- .../pegasys/web3signer/core/Eth2Runner.java | 2 + .../CommitBoostRequestSignatureRoute.java | 91 +++++++++++++++++++ .../CommitBoostPublicKeysHandler.java | 11 ++- .../CommitBoostRequestSignatureHandler.java | 88 ++++++++++++++++++ .../commitboost/CommitBoostSigner.java | 77 ++++++++++++++++ .../commitboost/SigningRootGenerator.java | 7 +- .../commitboost/json/PublicKeyMappings.java | 10 +- .../json/RequestSignatureBody.java | 21 +++++ .../commitboost/json/SignRequestType.java | 19 ++++ .../core/util/Web3SignerSigningRootUtil.java | 6 +- .../signing/ArtifactSignerProvider.java | 15 ++- .../config/DefaultArtifactSignerProvider.java | 25 +++-- .../SecpArtifactSignerProviderAdapter.java | 7 -- .../DefaultArtifactSignerProviderTest.java | 7 +- 14 files changed, 352 insertions(+), 34 deletions(-) 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/CommitBoostSigner.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/RequestSignatureBody.java create mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignRequestType.java 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 cf953367b..de12fbfa6 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; @@ -146,6 +147,7 @@ public void populateRouter(final Context context) { if (commitBoostParameters.isEnabled()) { new CommitBoostPublicKeysRoute(context).register(); new CommitBoostGenerateProxyKeyRoute(context, commitBoostParameters, eth2Spec).register(); + new CommitBoostRequestSignatureRoute(context, commitBoostParameters, 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..3241db2a6 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostRequestSignatureRoute.java @@ -0,0 +1,91 @@ +/* + * 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 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.CommitBoostParameters; +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 CommitBoostParameters commitBoostParameters; + private final Spec eth2Spec; + private final ArtifactSignerProvider artifactSignerProvider; + + public CommitBoostRequestSignatureRoute( + final Context context, + final CommitBoostParameters commitBoostParameters, + final Spec eth2Spec) { + this.context = context; + this.commitBoostParameters = commitBoostParameters; + this.eth2Spec = eth2Spec; + + // there should be only one DefaultArtifactSignerProvider in eth2 mode + artifactSignerProvider = + context.getArtifactSignerProviders().stream() + .filter(p -> p instanceof DefaultArtifactSignerProvider) + .findFirst() + .orElseThrow(); + } + + @Override + public void register() { + context + .getRouter() + .route(HttpMethod.POST, PATH) + .blockingHandler( + new CommitBoostRequestSignatureHandler( + artifactSignerProvider, commitBoostParameters, eth2Spec), + false) + .failureHandler(context.getErrorHandler()) + .failureHandler( + ctx -> { + final int statusCode = ctx.statusCode(); + if (statusCode == 400) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Bad Request") + .encode()); + } else if (statusCode == 404) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Identifier not found.") + .encode()); + } else if (statusCode == 500) { + ctx.response() + .setStatusCode(statusCode) + .end( + new JsonObject() + .put("code", statusCode) + .put("message", "Internal Server 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/CommitBoostPublicKeysHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java index 12a268195..623cb73e3 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; @@ -71,11 +72,11 @@ private PublicKeysResponse toPublicKeysResponse(final ArtifactSignerProvider pro private static PublicKeyMappings toPublicKeyMappings( final ArtifactSignerProvider provider, final String identifier) { - final Map> proxyIdentifiers = provider.getProxyIdentifiers(identifier); - final List proxyBlsPublicKeys = - proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> List.of()); - final List proxyEcdsaPublicKeys = - proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> List.of()); + final Map> proxyIdentifiers = provider.getProxyIdentifiers(identifier); + final Set proxyBlsPublicKeys = + proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> Set.of()); + final Set proxyEcdsaPublicKeys = + proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> Set.of()); return new PublicKeyMappings(identifier, proxyBlsPublicKeys, proxyEcdsaPublicKeys); } } 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..f90e4674a --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostRequestSignatureHandler.java @@ -0,0 +1,88 @@ +/* + * 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 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 tech.pegasys.web3signer.signing.config.CommitBoostParameters; + +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 static final int NOT_FOUND = 404; + private static final int BAD_REQUEST = 400; + private static final int INTERNAL_ERROR = 500; + + private final CommitBoostSigner commitBoostSigner; + private final SigningRootGenerator signingRootGenerator; + + public CommitBoostRequestSignatureHandler( + final ArtifactSignerProvider artifactSignerProvider, + final CommitBoostParameters commitBoostParameters, + final Spec eth2Spec) { + commitBoostSigner = new CommitBoostSigner(artifactSignerProvider); + this.signingRootGenerator = + new SigningRootGenerator(eth2Spec, commitBoostParameters.getGenesisValidatorsRoot()); + } + + @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(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(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(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(INTERNAL_ERROR, e); + } + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java new file mode 100644 index 000000000..a67ae7cd4 --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java @@ -0,0 +1,77 @@ +/* + * 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 tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; +import tech.pegasys.web3signer.signing.ArtifactSignature; +import tech.pegasys.web3signer.signing.ArtifactSigner; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; +import tech.pegasys.web3signer.signing.BlsArtifactSignature; +import tech.pegasys.web3signer.signing.KeyType; +import tech.pegasys.web3signer.signing.SecpArtifactSignature; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.tuweni.bytes.Bytes32; + +public class CommitBoostSigner { + private final ArtifactSignerProvider artifactSignerProvider; + + public CommitBoostSigner(final ArtifactSignerProvider artifactSignerProvider) { + this.artifactSignerProvider = artifactSignerProvider; + } + + public boolean isSignerAvailable(final String identifier, final SignRequestType type) { + return switch (type) { + case CONSENSUS -> artifactSignerProvider.availableIdentifiers().contains(identifier); + case PROXY_BLS -> { + final Map> proxyIdentifiers = + artifactSignerProvider.getProxyIdentifiers(identifier); + yield proxyIdentifiers.containsKey(KeyType.BLS) + && proxyIdentifiers.get(KeyType.BLS).contains(identifier); + } + case PROXY_ECDSA -> { + final Map> proxyIdentifiers = + artifactSignerProvider.getProxyIdentifiers(identifier); + yield proxyIdentifiers.containsKey(KeyType.SECP256K1) + && proxyIdentifiers.get(KeyType.SECP256K1).contains(identifier); + } + }; + } + + public Optional sign( + final String identifier, final SignRequestType type, final Bytes32 signingRoot) { + final Optional optionalArtifactSigner = + type == SignRequestType.CONSENSUS + ? artifactSignerProvider.getSigner(identifier) + : artifactSignerProvider.getProxySigner(identifier); + + return optionalArtifactSigner + .map( + signer -> { + final ArtifactSignature artifactSignature = signer.sign(signingRoot); + return switch (artifactSignature.getType()) { + case BLS -> + Optional.of( + ((BlsArtifactSignature) artifactSignature).getSignatureData().toString()); + case SECP256K1 -> + Optional.of( + SecpArtifactSignature.toBytes((SecpArtifactSignature) artifactSignature) + .toHexString()); + }; + }) + .orElse(Optional.empty()); + } +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 6dac4b768..4aee885e4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -20,7 +20,6 @@ import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import com.google.common.annotations.VisibleForTesting; -import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; public class SigningRootGenerator { @@ -34,13 +33,17 @@ public SigningRootGenerator(final Spec eth2Spec, final Bytes32 genesisValidators COMMIT_BOOST_DOMAIN, genesisForkVersion, genesisValidatorsRoot); } - public Bytes computeSigningRoot( + public Bytes32 computeSigningRoot( final ProxyDelegation proxyDelegation, final ProxyKeySignatureScheme scheme) { final Merkleizable proxyDelegationMerkleizable = proxyDelegation.toMerkleizable(scheme); return Web3SignerSigningRootUtil.computeSigningRoot(proxyDelegationMerkleizable, domain); } + public Bytes32 computeSigningRoot(final Bytes32 objectRoot) { + return Web3SignerSigningRootUtil.computeSigningRoot(objectRoot, domain); + } + @VisibleForTesting Bytes32 getDomain() { return domain; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java index 08ca0690b..630adc955 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/PublicKeyMappings.java @@ -12,7 +12,7 @@ */ package tech.pegasys.web3signer.core.service.http.handlers.commitboost.json; -import java.util.List; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonProperty; @@ -20,10 +20,10 @@ * Represents the public key mappings for get_pubkeys API * * @param consensus BLS Public Key in hex string format - * @param proxyBlsPublicKeys List of Proxy BLS Public Key in hex string format - * @param proxyEcdsaPublicKeys List of Proxy ECDSA (SECP256K1) Public Key in hex string format + * @param proxyBlsPublicKeys Set of Proxy BLS Public Key in hex string format + * @param proxyEcdsaPublicKeys Set of Proxy ECDSA (SECP256K1) Public Key in hex string format */ public record PublicKeyMappings( @JsonProperty(value = "consensus") String consensus, - @JsonProperty(value = "proxy_bls") List proxyBlsPublicKeys, - @JsonProperty(value = "proxy_ecdsa") List proxyEcdsaPublicKeys) {} + @JsonProperty(value = "proxy_bls") Set proxyBlsPublicKeys, + @JsonProperty(value = "proxy_ecdsa") Set proxyEcdsaPublicKeys) {} 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..f8087d224 --- /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) SignRequestType type, + @JsonProperty(value = "pubkey", required = true) String publicKey, + @JsonProperty(value = "object_root", required = true) Bytes32 objectRoot) {} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignRequestType.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignRequestType.java new file mode 100644 index 000000000..c5b413d2a --- /dev/null +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/json/SignRequestType.java @@ -0,0 +1,19 @@ +/* + * 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; + +public enum SignRequestType { + CONSENSUS, + PROXY_BLS, + PROXY_ECDSA +} diff --git a/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java b/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java index 39e1b536b..485fdba12 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/util/Web3SignerSigningRootUtil.java @@ -21,10 +21,14 @@ import org.apache.tuweni.bytes.Bytes32; public class Web3SignerSigningRootUtil { - public static Bytes computeSigningRoot(final Merkleizable object, final Bytes32 domain) { + public static Bytes32 computeSigningRoot(final Merkleizable object, final Bytes32 domain) { return new SigningData(object.hashTreeRoot(), domain).hashTreeRoot(); } + public static Bytes32 computeSigningRoot(final Bytes32 objectRoot, final Bytes32 domain) { + return new SigningData(objectRoot, domain).hashTreeRoot(); + } + public static Bytes32 computeDomain( final Bytes4 domainType, final Bytes4 forkVersion, final Bytes32 genesisValidatorsRoot) { final Bytes32 forkDataRoot = computeForkDataRoot(forkVersion, genesisValidatorsRoot); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java index 2ecefe42d..21ea2972b 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java @@ -13,7 +13,6 @@ package tech.pegasys.web3signer.signing; import java.io.Closeable; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -36,6 +35,16 @@ public interface ArtifactSignerProvider extends Closeable { */ Optional getSigner(final String identifier); + /** + * Get the proxy signer for the given identifier. + * + * @param identifier the identifier of the signer + * @return the signer or empty if no signer is found + */ + default Optional getProxySigner(final String identifier) { + throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); + } + /** * Get the available identifiers for the loaded signers. * @@ -49,7 +58,9 @@ public interface ArtifactSignerProvider extends Closeable { * @param identifier the identifier of the signer * @return Map of Key Type (BLS, SECP256K1) and corresponding proxy identifiers */ - Map> getProxyIdentifiers(final String identifier); + default Map> getProxyIdentifiers(final String identifier) { + throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); + } /** * Add a new signer to the signer provider. diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 5979bfa11..3567e633f 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -24,10 +24,9 @@ import java.io.File; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.List; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -47,7 +46,7 @@ public class DefaultArtifactSignerProvider implements ArtifactSignerProvider { private static final Logger LOG = LogManager.getLogger(); private final Supplier> artifactSignerCollectionSupplier; private final Map signers = new HashMap<>(); - private final Map> proxySigners = new HashMap<>(); + private final Map> proxySigners = new HashMap<>(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private final Optional commitBoostParameters; @@ -116,20 +115,28 @@ public Optional getSigner(final String identifier) { return result; } + @Override + public Optional getProxySigner(final String identifier) { + return proxySigners.values().stream() + .flatMap(Set::stream) + .filter(signer -> signer.getIdentifier().equals(identifier)) + .findFirst(); + } + @Override public Set availableIdentifiers() { return Set.copyOf(signers.keySet()); } @Override - public Map> getProxyIdentifiers(final String identifier) { - final List artifactSigners = - proxySigners.computeIfAbsent(identifier, k -> List.of()); + public Map> getProxyIdentifiers(final String identifier) { + final Set artifactSigners = + proxySigners.computeIfAbsent(identifier, k -> Set.of()); return artifactSigners.stream() .collect( Collectors.groupingBy( ArtifactSigner::getKeyType, - Collectors.mapping(ArtifactSigner::getIdentifier, Collectors.toList()))); + Collectors.mapping(ArtifactSigner::getIdentifier, Collectors.toSet()))); } @Override @@ -157,7 +164,7 @@ public Future removeSigner(final String identifier) { public Future addProxySigner(final ArtifactSigner signer, final String identifier) { return executorService.submit( () -> { - proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).add(signer); + proxySigners.computeIfAbsent(identifier, k -> new HashSet<>()).add(signer); LOG.info( "Loaded new proxy signer {} for identifier '{}'", signer.getIdentifier(), identifier); return null; @@ -188,7 +195,7 @@ private void loadProxySigners( final MappedResults signersResult = loaderFunction.apply(proxyDir, keystoreParameter.getProxyKeystoresPasswordFile()); final Collection signers = signersResult.getValues(); - proxySigners.computeIfAbsent(identifier, k -> new ArrayList<>()).addAll(signers); + proxySigners.computeIfAbsent(identifier, k -> new HashSet<>()).addAll(signers); } } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java index 36578bcdf..9155694ba 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/SecpArtifactSignerProviderAdapter.java @@ -17,10 +17,8 @@ import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.KeyType; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -68,11 +66,6 @@ public Set availableIdentifiers() { return Set.copyOf(signers.keySet()); } - @Override - public Map> getProxyIdentifiers(final String identifier) { - throw new NotImplementedException(); - } - @Override public Future addSigner(final ArtifactSigner signer) { throw new NotImplementedException(); diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java index 30466e6a5..36900d8f2 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProviderTest.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.IntStream; import org.junit.jupiter.api.AfterEach; @@ -141,7 +142,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); // assert that the proxy keys are loaded correctly - final Map> key1ProxyPublicKeys = + final Map> key1ProxyPublicKeys = signerProvider.getProxyIdentifiers(PUBLIC_KEY1); assertThat(key1ProxyPublicKeys.get(KeyType.BLS)) @@ -149,7 +150,7 @@ void proxySignersAreLoadedCorrectly() throws IOException { assertThat(key1ProxyPublicKeys.get(KeyType.SECP256K1)) .containsExactlyInAnyOrder(getCompressedSecpPublicKeysArray(key1SecpKeyPairs)); - final Map> key2ProxyPublicKeys = + final Map> key2ProxyPublicKeys = signerProvider.getProxyIdentifiers(PUBLIC_KEY2); assertThat(key2ProxyPublicKeys.get(KeyType.BLS)) @@ -178,7 +179,7 @@ void emptyProxySignersAreLoadedSuccessfully() { assertThatCode(() -> signerProvider.load().get()).doesNotThrowAnyException(); for (String identifier : List.of(PUBLIC_KEY1, PUBLIC_KEY2)) { - final Map> keyProxyPublicKeys = + final Map> keyProxyPublicKeys = signerProvider.getProxyIdentifiers(identifier); assertThat(keyProxyPublicKeys).isEmpty(); } From d0c1119d3bf152aa4d21def355ff09f59021ddab Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 4 Nov 2024 10:16:19 +1000 Subject: [PATCH 42/57] refactor code --- .../CommitBoostGenerateProxyKeyHandler.java | 3 ++- .../CommitBoostRequestSignatureHandler.java | 2 +- .../commitboost/CommitBoostSigner.java | 4 ++++ .../commitboost/SigningRootGenerator.java | 22 ++++++++++--------- .../CommitBoostSigningRootTest.java | 6 +++-- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 58f9ee1ba..98f9cd1fc 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -90,7 +90,8 @@ public void handle(final RoutingContext context) { final ProxyDelegation proxyDelegation = new ProxyDelegation(identifier, artifactSigner.getIdentifier()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyDelegation, proxyKeyBody.scheme()); + signingRootGenerator.computeSigningRoot( + proxyDelegation.toMerkleizable(proxyKeyBody.scheme()).hashTreeRoot()); final Optional optionalSig = signerForIdentifier.sign(identifier, signingRoot); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); 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 index f90e4674a..561a8dfb6 100644 --- 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 @@ -44,7 +44,7 @@ public CommitBoostRequestSignatureHandler( final CommitBoostParameters commitBoostParameters, final Spec eth2Spec) { commitBoostSigner = new CommitBoostSigner(artifactSignerProvider); - this.signingRootGenerator = + signingRootGenerator = new SigningRootGenerator(eth2Spec, commitBoostParameters.getGenesisValidatorsRoot()); } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java index a67ae7cd4..f90b21750 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigner.java @@ -26,6 +26,10 @@ import org.apache.tuweni.bytes.Bytes32; +/** + * This class wraps the {@link ArtifactSignerProvider} and provides a way to check if a signer is + * available, consensus or proxy,and to sign a message. + */ public class CommitBoostSigner { private final ArtifactSignerProvider artifactSignerProvider; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java index 4aee885e4..1e302bbb4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/SigningRootGenerator.java @@ -13,15 +13,18 @@ package tech.pegasys.web3signer.core.service.http.handlers.commitboost; import tech.pegasys.teku.infrastructure.bytes.Bytes4; -import tech.pegasys.teku.infrastructure.ssz.Merkleizable; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; -import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; import tech.pegasys.web3signer.core.util.Web3SignerSigningRootUtil; import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes32; +/** + * Generates the signing root for a given object root using the commit boost domain. + * + *

The commit boost domain is computed using the genesis validators root and the genesis fork + * version. + */ public class SigningRootGenerator { private static final Bytes4 COMMIT_BOOST_DOMAIN = Bytes4.fromHexString("0x6d6d6f43"); private final Bytes32 domain; @@ -33,13 +36,12 @@ public SigningRootGenerator(final Spec eth2Spec, final Bytes32 genesisValidators COMMIT_BOOST_DOMAIN, genesisForkVersion, genesisValidatorsRoot); } - public Bytes32 computeSigningRoot( - final ProxyDelegation proxyDelegation, final ProxyKeySignatureScheme scheme) { - final Merkleizable proxyDelegationMerkleizable = proxyDelegation.toMerkleizable(scheme); - - return Web3SignerSigningRootUtil.computeSigningRoot(proxyDelegationMerkleizable, domain); - } - + /** + * Computes the signing root for a given object root using commit boost domain. + * + * @param objectRoot the object root to compute the signing root for + * @return the signing root + */ public Bytes32 computeSigningRoot(final Bytes32 objectRoot) { return Web3SignerSigningRootUtil.computeSigningRoot(objectRoot, domain); } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java index e2a3e313a..1d03dfa72 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java @@ -148,7 +148,8 @@ void computeSigningRootForBLSProxyKey(final Eth2Network network) { final ProxyDelegation proxyDelegation = new ProxyDelegation(DELEGATOR_PUB_KEY.toHexString(), BLS_PROXY_PUB_KEY.toHexString()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyDelegation, ProxyKeySignatureScheme.BLS); + signingRootGenerator.computeSigningRoot( + proxyDelegation.toMerkleizable(ProxyKeySignatureScheme.BLS).hashTreeRoot()); assertThat(signingRoot).isEqualTo(BLS_PROXY_ROOT_MAP.get(network)); @@ -169,7 +170,8 @@ void computeSigningRootforSECPProxyKey(final Eth2Network network) { new ProxyDelegation(DELEGATOR_PUB_KEY.toHexString(), SECP_PROXY_PUB_KEY_ENC.toHexString()); final Bytes signingRoot = - signingRootGenerator.computeSigningRoot(proxyDelegation, ProxyKeySignatureScheme.ECDSA); + signingRootGenerator.computeSigningRoot( + proxyDelegation.toMerkleizable(ProxyKeySignatureScheme.ECDSA).hashTreeRoot()); assertThat(signingRoot).isEqualTo(SECP_PROXY_ROOT_MAP.get(network)); From b8f7910ec7654f16c842f446b70f1bd71031775e Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 4 Nov 2024 10:23:23 +1000 Subject: [PATCH 43/57] rename to CommitBoostSignerProvider --- .../commitboost/CommitBoostRequestSignatureHandler.java | 4 ++-- ...{CommitBoostSigner.java => CommitBoostSignerProvider.java} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/{CommitBoostSigner.java => CommitBoostSignerProvider.java} (96%) 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 index 561a8dfb6..4354c8742 100644 --- 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 @@ -36,14 +36,14 @@ public class CommitBoostRequestSignatureHandler implements Handler Date: Mon, 4 Nov 2024 10:30:20 +1000 Subject: [PATCH 44/57] refactor public keys route and handler -- extract artifactsignerprovider in route instead of handler --- .../routes/eth2/CommitBoostPublicKeysRoute.java | 11 ++++++++++- .../commitboost/CommitBoostPublicKeysHandler.java | 15 +++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java index 261290049..abdc3cb47 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostPublicKeysRoute.java @@ -15,6 +15,8 @@ import tech.pegasys.web3signer.core.Context; import tech.pegasys.web3signer.core.routes.Web3SignerRoute; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.CommitBoostPublicKeysHandler; +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; @@ -23,9 +25,16 @@ public class CommitBoostPublicKeysRoute implements Web3SignerRoute { private static final String PATH = "/signer/v1/get_pubkeys"; private final Context context; + private final ArtifactSignerProvider artifactSignerProvider; public CommitBoostPublicKeysRoute(final Context context) { this.context = context; + // there should be only one DefaultArtifactSignerProvider in eth2 mode + artifactSignerProvider = + context.getArtifactSignerProviders().stream() + .filter(p -> p instanceof DefaultArtifactSignerProvider) + .findFirst() + .orElseThrow(); } @Override @@ -36,7 +45,7 @@ public void register() { .produces(JSON_HEADER) .handler( new BlockingHandlerDecorator( - new CommitBoostPublicKeysHandler(context.getArtifactSignerProviders()), false)) + new CommitBoostPublicKeysHandler(artifactSignerProvider), false)) .failureHandler(context.getErrorHandler()) .failureHandler( ctx -> { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java index 623cb73e3..95820e4d8 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java @@ -20,9 +20,7 @@ import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.PublicKeysResponse; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; import tech.pegasys.web3signer.signing.KeyType; -import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; @@ -36,22 +34,15 @@ public class CommitBoostPublicKeysHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); - private final List artifactSignerProviders; + private final ArtifactSignerProvider artifactSignerProvider; private final ObjectMapper objectMapper = SigningObjectMapperFactory.createObjectMapper(); - public CommitBoostPublicKeysHandler(final List artifactSignerProviders) { - this.artifactSignerProviders = artifactSignerProviders; + public CommitBoostPublicKeysHandler(final ArtifactSignerProvider artifactSignerProvider) { + this.artifactSignerProvider = artifactSignerProvider; } @Override public void handle(final RoutingContext context) { - // obtain DefaultArtifactSignerProvider as that is the only one we are dealing in eth2 mode. - final ArtifactSignerProvider artifactSignerProvider = - artifactSignerProviders.stream() - .filter(provider -> provider instanceof DefaultArtifactSignerProvider) - .findFirst() - .orElseThrow(); - final PublicKeysResponse publicKeysResponse = toPublicKeysResponse(artifactSignerProvider); try { final String jsonEncoded = objectMapper.writeValueAsString(publicKeysResponse); From 71435c931738ea150251b237452f59fa7cf889f4 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 09:32:04 +1000 Subject: [PATCH 45/57] Use CommitBoostSignerProvider --- .../CommitBoostGenerateProxyKeyRoute.java | 15 +++----- .../CommitBoostGenerateProxyKeyHandler.java | 36 ++++++++++--------- .../CommitBoostSignerProvider.java | 25 +++++++++++++ .../handlers/signing/SignerForIdentifier.java | 9 ----- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java index bd5156ed4..69d1e8330 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/CommitBoostGenerateProxyKeyRoute.java @@ -12,15 +12,11 @@ */ package tech.pegasys.web3signer.core.routes.eth2; -import static tech.pegasys.web3signer.signing.KeyType.BLS; - 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.CommitBoostGenerateProxyKeyHandler; -import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.BlsArtifactSignature; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; @@ -30,7 +26,7 @@ public class CommitBoostGenerateProxyKeyRoute implements Web3SignerRoute { private static final String PATH = "/signer/v1/generate_proxy_key"; private final Context context; - private final SignerForIdentifier blsSigner; + private final ArtifactSignerProvider artifactSignerProvider; private final CommitBoostParameters commitBoostParameters; private final Spec eth2Spec; @@ -43,15 +39,11 @@ public CommitBoostGenerateProxyKeyRoute( this.eth2Spec = eth2Spec; // there should be only one DefaultArtifactSignerProvider in eth2 mode - final ArtifactSignerProvider artifactSignerProvider = + artifactSignerProvider = context.getArtifactSignerProviders().stream() .filter(p -> p instanceof DefaultArtifactSignerProvider) .findFirst() .orElseThrow(); - - blsSigner = - new SignerForIdentifier<>( - artifactSignerProvider, sig -> sig.getSignatureData().toString(), BLS); } @Override @@ -60,7 +52,8 @@ public void register() { .getRouter() .route(HttpMethod.POST, PATH) .blockingHandler( - new CommitBoostGenerateProxyKeyHandler(blsSigner, commitBoostParameters, eth2Spec), + new CommitBoostGenerateProxyKeyHandler( + artifactSignerProvider, commitBoostParameters, eth2Spec), false) .failureHandler(context.getErrorHandler()) .failureHandler( diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 98f9cd1fc..18ed3b541 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -20,9 +20,10 @@ import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.GenerateProxyKeyBody; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyDelegation; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignedProxyDelegation; -import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.signing.ArtifactSigner; +import tech.pegasys.web3signer.signing.ArtifactSignerProvider; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import java.util.Optional; @@ -33,7 +34,7 @@ import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); @@ -42,17 +43,17 @@ public class CommitBoostGenerateProxyKeyHandler implements Handler signerForIdentifier; private final ProxyKeyGenerator proxyKeyGenerator; private final SigningRootGenerator signingRootGenerator; + private final CommitBoostSignerProvider commitBoostSignerProvider; public CommitBoostGenerateProxyKeyHandler( - final SignerForIdentifier signerForIdentifier, + final ArtifactSignerProvider artifactSignerProvider, final CommitBoostParameters commitBoostParameters, final Spec eth2Spec) { - this.signerForIdentifier = signerForIdentifier; - this.proxyKeyGenerator = new ProxyKeyGenerator(commitBoostParameters); - this.signingRootGenerator = + commitBoostSignerProvider = new CommitBoostSignerProvider(artifactSignerProvider); + proxyKeyGenerator = new ProxyKeyGenerator(commitBoostParameters); + signingRootGenerator = new SigningRootGenerator(eth2Spec, commitBoostParameters.getGenesisValidatorsRoot()); } @@ -71,28 +72,31 @@ public void handle(final RoutingContext context) { // Check for identifier, if not exist, fail with 404 final String identifier = normaliseIdentifier(proxyKeyBody.blsPublicKey()); - if (!signerForIdentifier.isSignerAvailable(identifier)) { + final boolean signerAvailable = + commitBoostSignerProvider.isSignerAvailable(identifier, SignRequestType.CONSENSUS); + if (!signerAvailable) { context.fail(NOT_FOUND); return; } - // Generate actual proxy key and encrypted keystore based on signature scheme - final ArtifactSigner artifactSigner; try { - artifactSigner = + // Generate actual proxy key and encrypted keystore based on signature scheme + final ArtifactSigner proxyArtifactSigner = switch (proxyKeyBody.scheme()) { case BLS -> proxyKeyGenerator.generateBLSProxyKey(identifier); case ECDSA -> proxyKeyGenerator.generateECProxyKey(identifier); }; - // Add generated proxy key to DefaultArtifactSignerProvider - signerForIdentifier.getSignerProvider().addProxySigner(artifactSigner, identifier).get(); + + // Add generated proxy ArtifactSigner to ArtifactSignerProvider + commitBoostSignerProvider.addProxySigner(proxyArtifactSigner, identifier); final ProxyDelegation proxyDelegation = - new ProxyDelegation(identifier, artifactSigner.getIdentifier()); - final Bytes signingRoot = + new ProxyDelegation(identifier, proxyArtifactSigner.getIdentifier()); + final Bytes32 signingRoot = signingRootGenerator.computeSigningRoot( proxyDelegation.toMerkleizable(proxyKeyBody.scheme()).hashTreeRoot()); - final Optional optionalSig = signerForIdentifier.sign(identifier, signingRoot); + final Optional optionalSig = + commitBoostSignerProvider.sign(identifier, SignRequestType.CONSENSUS, signingRoot); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); return; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java index 4136a59be..23c017d18 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java @@ -33,10 +33,22 @@ public class CommitBoostSignerProvider { private final ArtifactSignerProvider artifactSignerProvider; + /** + * Constructor for the CommitBoostSignerProvider + * + * @param artifactSignerProvider The {@link ArtifactSignerProvider} to use for signing + */ public CommitBoostSignerProvider(final ArtifactSignerProvider artifactSignerProvider) { this.artifactSignerProvider = artifactSignerProvider; } + /** + * Check if a signer is available for the given identifier and type + * + * @param identifier The identifier to check + * @param type The type of signer to check + * @return true if a signer is available, false otherwise + */ public boolean isSignerAvailable(final String identifier, final SignRequestType type) { return switch (type) { case CONSENSUS -> artifactSignerProvider.availableIdentifiers().contains(identifier); @@ -55,6 +67,15 @@ public boolean isSignerAvailable(final String identifier, final SignRequestType }; } + /** + * Sign a message with the given identifier and type + * + * @param identifier The identifier to sign with + * @param type The type of signer to use + * @param signingRoot The root to sign + * @return An optional string of the signature in hex format. Empty if no signer available for + * given identifier + */ public Optional sign( final String identifier, final SignRequestType type, final Bytes32 signingRoot) { final Optional optionalArtifactSigner = @@ -78,4 +99,8 @@ public Optional sign( }) .orElse(Optional.empty()); } + + public void addProxySigner(final ArtifactSigner artifactSigner, final String identifier) { + artifactSignerProvider.addProxySigner(artifactSigner, identifier); + } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java index 5776805e4..7148fb6fd 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java @@ -56,15 +56,6 @@ public Optional signAndGetArtifactSignature(final String identifier, final By return signerProvider.getSigner(identifier).map(signer -> (T) signer.sign(data)); } - /** - * Get the signer provider - * - * @return signer provider - */ - public ArtifactSignerProvider getSignerProvider() { - return signerProvider; - } - /** * Converts hex string to bytes * From 4cd9ef72be6a683a244efdcac547f76c7bd3aae6 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 10:01:22 +1000 Subject: [PATCH 46/57] Simplify SignerForIdentifier --- .../core/routes/eth1/Eth1SignRoute.java | 7 ++-- .../core/routes/eth1/JsonRpcRoute.java | 31 ++++++----------- .../routes/eth2/Eth2SignExtensionRoute.java | 16 ++++----- .../core/routes/eth2/Eth2SignRoute.java | 14 ++++---- .../handlers/signing/SignerForIdentifier.java | 34 ++++++------------- .../web3signer/signing/ArtifactSignature.java | 12 +++++++ .../signing/BlsArtifactSignature.java | 4 +++ .../signing/SecpArtifactSignature.java | 10 ++++++ 8 files changed, 65 insertions(+), 63 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/Eth1SignRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/Eth1SignRoute.java index cdd1db7f1..ba4b3d9cd 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/Eth1SignRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/Eth1SignRoute.java @@ -20,7 +20,6 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider; import java.util.Optional; @@ -33,7 +32,7 @@ public class Eth1SignRoute implements Web3SignerRoute { private final Context context; private final ArtifactSignerProvider signerProvider; - private final SignerForIdentifier secpSigner; + private final SignerForIdentifier secpSigner; public Eth1SignRoute(final Context context) { this.context = context; @@ -46,9 +45,7 @@ public Eth1SignRoute(final Context context) { if (first.isPresent()) { signerProvider = first.get(); - secpSigner = - new SignerForIdentifier<>( - signerProvider, sig -> SecpArtifactSignature.toBytes(sig).toHexString(), SECP256K1); + secpSigner = new SignerForIdentifier(signerProvider); } else { throw new IllegalStateException( "No DefaultArtifactSignerProvider found in Context for eth1 mode"); diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/JsonRpcRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/JsonRpcRoute.java index ffefa21b3..f78a72a2f 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/JsonRpcRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth1/JsonRpcRoute.java @@ -12,8 +12,6 @@ */ package tech.pegasys.web3signer.core.routes.eth1; -import static tech.pegasys.web3signer.signing.KeyType.SECP256K1; - import tech.pegasys.web3signer.core.Context; import tech.pegasys.web3signer.core.Runner; import tech.pegasys.web3signer.core.WebClientOptionsFactory; @@ -38,11 +36,8 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.SendTransactionHandler; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.TransactionFactory; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import tech.pegasys.web3signer.signing.config.SecpArtifactSignerProviderAdapter; -import java.util.Optional; - import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpMethod; @@ -62,18 +57,17 @@ public class JsonRpcRoute implements Web3SignerRoute { public JsonRpcRoute(final Context context, final Eth1Config eth1Config) { this.context = context; - // we need signerProvider which is an instance of SecpArtifactSignerProviderAdapter - final Optional first = + // we need signerProvider which is an instance of SecpArtifactSignerProviderAdapter which uses + // eth1 address as identifier + final ArtifactSignerProvider signerProvider = context.getArtifactSignerProviders().stream() .filter(provider -> provider instanceof SecpArtifactSignerProviderAdapter) - .findFirst(); - final ArtifactSignerProvider signerProvider; - if (first.isPresent()) { - signerProvider = first.get(); - } else { - throw new IllegalStateException( - "No SecpArtifactSignerProviderAdapter found in Context for eth1 mode"); - } + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "No SecpArtifactSignerProviderAdapter found in Context for eth1 mode")); + // use same instance of downstreamHttpClient and path calculator for all requests final HttpClient downstreamHttpClient = createDownstreamHttpClient(eth1Config, context.getVertx()); @@ -126,11 +120,8 @@ private static RequestMapper createRequestMapper( final long chainId) { final PassThroughHandler defaultHandler = new PassThroughHandler(transmitterFactory, JSON_DECODER); - final SignerForIdentifier secpSigner = - new SignerForIdentifier<>( - signerProviderMappedToEth1Address, - sig -> SecpArtifactSignature.toBytes(sig).toHexString(), - SECP256K1); + final SignerForIdentifier secpSigner = + new SignerForIdentifier(signerProviderMappedToEth1Address); final TransactionFactory transactionFactory = new TransactionFactory(chainId, JSON_DECODER, transmitterFactory); final SendTransactionHandler sendTransactionHandler = diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignExtensionRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignExtensionRoute.java index c5caac682..a338b95d8 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignExtensionRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignExtensionRoute.java @@ -12,14 +12,11 @@ */ package tech.pegasys.web3signer.core.routes.eth2; -import static tech.pegasys.web3signer.signing.KeyType.BLS; - import tech.pegasys.web3signer.core.Context; import tech.pegasys.web3signer.core.routes.Web3SignerRoute; import tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier; import tech.pegasys.web3signer.core.service.http.handlers.signing.SigningExtensionHandler; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.BlsArtifactSignature; import io.vertx.core.http.HttpMethod; import io.vertx.core.json.JsonObject; @@ -28,7 +25,7 @@ public class Eth2SignExtensionRoute implements Web3SignerRoute { public static final String SIGN_EXT_PATH = "/api/v1/eth2/ext/sign/:identifier"; private final Context context; - private final SignerForIdentifier blsSigner; + private final SignerForIdentifier blsSigner; public Eth2SignExtensionRoute(final Context context) { this.context = context; @@ -36,11 +33,14 @@ public Eth2SignExtensionRoute(final Context context) { // there should be only one ArtifactSignerProvider in eth2 mode at the moment which is of BLS // types. final ArtifactSignerProvider artifactSignerProvider = - context.getArtifactSignerProviders().stream().findFirst().orElseThrow(); + context.getArtifactSignerProviders().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "No ArtifactSignerProvider found in Context for eth2 mode")); - blsSigner = - new SignerForIdentifier<>( - artifactSignerProvider, sig -> sig.getSignatureData().toString(), BLS); + blsSigner = new SignerForIdentifier(artifactSignerProvider); } @Override diff --git a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignRoute.java b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignRoute.java index c54e42da6..27aaa2c38 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignRoute.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/routes/eth2/Eth2SignRoute.java @@ -23,7 +23,6 @@ import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SignForIdentifierHandler; import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.BlsArtifactSignature; import tech.pegasys.web3signer.slashingprotection.SlashingProtection; import tech.pegasys.web3signer.slashingprotection.SlashingProtectionContext; @@ -36,7 +35,7 @@ public class Eth2SignRoute implements Web3SignerRoute { private static final String SIGN_PATH = "/api/v1/eth2/sign/:identifier"; private final Context context; - private final SignerForIdentifier blsSigner; + private final SignerForIdentifier blsSigner; private final ObjectMapper objectMapper = SigningObjectMapperFactory.createObjectMapper(); private final Spec eth2Spec; private final Optional slashingProtection; @@ -52,11 +51,14 @@ public Eth2SignRoute( // there should be only one ArtifactSignerProvider in eth2 mode at the moment which is of BLS // types. final ArtifactSignerProvider artifactSignerProvider = - context.getArtifactSignerProviders().stream().findFirst().orElseThrow(); + context.getArtifactSignerProviders().stream() + .findFirst() + .orElseThrow( + () -> + new IllegalStateException( + "No ArtifactSignerProvider found in Context for eth2 mode")); - blsSigner = - new SignerForIdentifier<>( - artifactSignerProvider, sig -> sig.getSignatureData().toString(), BLS); + blsSigner = new SignerForIdentifier(artifactSignerProvider); } @Override diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java index 7148fb6fd..745bd2d3a 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java @@ -14,7 +14,6 @@ import tech.pegasys.web3signer.signing.ArtifactSignature; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.KeyType; import java.util.Optional; @@ -23,19 +22,16 @@ import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; -public class SignerForIdentifier { +/** + * This class wraps the {@link ArtifactSignerProvider} and provides a way to check if a signer is + * available for a given identifier and to sign a message. + */ +public class SignerForIdentifier { private static final Logger LOG = LogManager.getLogger(); private final ArtifactSignerProvider signerProvider; - private final SignatureFormatter signatureFormatter; - private final KeyType type; - public SignerForIdentifier( - final ArtifactSignerProvider signerProvider, - final SignatureFormatter signatureFormatter, - final KeyType type) { + public SignerForIdentifier(final ArtifactSignerProvider signerProvider) { this.signerProvider = signerProvider; - this.signatureFormatter = signatureFormatter; - this.type = type; } /** @@ -48,12 +44,12 @@ public SignerForIdentifier( * @throws IllegalArgumentException if data is invalid i.e. not a valid hex string, null or empty. */ public Optional sign(final String identifier, final Bytes data) { - return signerProvider.getSigner(identifier).map(signer -> formatSignature(signer.sign(data))); + return signerProvider.getSigner(identifier).map(signer -> signer.sign(data).asHex()); } - @SuppressWarnings("unchecked") - public Optional signAndGetArtifactSignature(final String identifier, final Bytes data) { - return signerProvider.getSigner(identifier).map(signer -> (T) signer.sign(data)); + public Optional signAndGetArtifactSignature( + final String identifier, final Bytes data) { + return signerProvider.getSigner(identifier).map(signer -> signer.sign(data)); } /** @@ -77,16 +73,6 @@ public static Bytes toBytes(final String data) { return dataToSign; } - @SuppressWarnings("unchecked") - private String formatSignature(final ArtifactSignature signature) { - if (signature.getType() == type) { - final T artifactSignature = (T) signature; - return signatureFormatter.format(artifactSignature); - } else { - throw new IllegalStateException("Invalid signature type"); - } - } - /** * Checks whether a signer for the passed identifier is present * diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignature.java index 00232fef0..2b9b5afd4 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignature.java @@ -14,5 +14,17 @@ public interface ArtifactSignature { + /** + * Returns the type of key used to sign the artifact + * + * @return the type of key used to sign the artifact + */ KeyType getType(); + + /** + * Returns the signature data in hex format + * + * @return the signature data in hex format + */ + String asHex(); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java index 00ce131ce..734026f65 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java @@ -29,4 +29,8 @@ public KeyType getType() { public BLSSignature getSignatureData() { return blsSignature; } + + public String asHex() { + return blsSignature.toString(); + } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java index 236288642..6c1d6916f 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java @@ -35,6 +35,15 @@ public Signature getSignatureData() { return signature; } + @Override + public String asHex() { + return Bytes.concatenate( + Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getR()))), + Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getS()))), + Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getV()))) + .toHexString(); + } + public static SecpArtifactSignature fromBytes(final Bytes signature) { final Bytes r = signature.slice(0, 32); final Bytes s = signature.slice(32, 32); @@ -46,6 +55,7 @@ public static SecpArtifactSignature fromBytes(final Bytes signature) { Numeric.toBigInt(s.toArrayUnsafe()))); } + @Deprecated // use asHex instead public static Bytes toBytes(final SecpArtifactSignature signature) { final Signature signatureData = signature.getSignatureData(); return Bytes.concatenate( From ad2e0eff6ad1c4c39291b90341347679d07a02bb Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 10:11:41 +1000 Subject: [PATCH 47/57] Delete SignatureFormatter --- .../handlers/signing/SignatureFormatter.java | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignatureFormatter.java diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignatureFormatter.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignatureFormatter.java deleted file mode 100644 index 103c531c5..000000000 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignatureFormatter.java +++ /dev/null @@ -1,21 +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.handlers.signing; - -import tech.pegasys.web3signer.signing.ArtifactSignature; - -@FunctionalInterface -public interface SignatureFormatter { - - String format(T signature); -} From 30c6f1b990158fa23722e77adf3b738046b04e03 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 10:38:14 +1000 Subject: [PATCH 48/57] cleaning SignerForIdentifier generic usage --- .../CommitBoostGenerateProxyKeyHandler.java | 1 - .../CommitBoostSignerProvider.java | 19 +------------------ .../signing/Eth1SignForIdentifierHandler.java | 4 ++-- .../signing/SigningExtensionHandler.java | 4 ++-- .../eth2/Eth2SignForIdentifierHandler.java | 4 ++-- .../EthSignResultProvider.java | 4 ++-- .../EthSignTransactionResultProvider.java | 4 ++-- .../EthSignTypedDataResultProvider.java | 4 ++-- .../SendTransactionHandler.java | 6 +++--- .../signing/TransactionSerializer.java | 13 +++++++------ .../CommitBoostSigningRootTest.java | 4 ++-- .../jsonrpc/EthSignResultProviderTest.java | 17 ++++++++--------- .../EthSignTypedDataResultProviderTest.java | 15 +++++++-------- .../signing/BlsArtifactSignature.java | 1 + .../signing/SecpArtifactSignature.java | 9 --------- .../signing/BlsArtifactSignatureTest.java | 2 +- .../signing/BlsArtifactSignerTest.java | 2 +- 17 files changed, 43 insertions(+), 70 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index 18ed3b541..c21787213 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -37,7 +37,6 @@ import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { - private static final Logger LOG = LogManager.getLogger(); private static final ObjectMapper JSON_MAPPER = SigningObjectMapperFactory.createObjectMapper(); private static final int NOT_FOUND = 404; private static final int BAD_REQUEST = 400; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java index 23c017d18..c2276f9cd 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java @@ -13,12 +13,9 @@ package tech.pegasys.web3signer.core.service.http.handlers.commitboost; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; -import tech.pegasys.web3signer.signing.ArtifactSignature; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.BlsArtifactSignature; import tech.pegasys.web3signer.signing.KeyType; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import java.util.Map; import java.util.Optional; @@ -83,21 +80,7 @@ public Optional sign( ? artifactSignerProvider.getSigner(identifier) : artifactSignerProvider.getProxySigner(identifier); - return optionalArtifactSigner - .map( - signer -> { - final ArtifactSignature artifactSignature = signer.sign(signingRoot); - return switch (artifactSignature.getType()) { - case BLS -> - Optional.of( - ((BlsArtifactSignature) artifactSignature).getSignatureData().toString()); - case SECP256K1 -> - Optional.of( - SecpArtifactSignature.toBytes((SecpArtifactSignature) artifactSignature) - .toHexString()); - }; - }) - .orElse(Optional.empty()); + return optionalArtifactSigner.map(signer -> signer.sign(signingRoot).asHex()); } public void addProxySigner(final ArtifactSigner artifactSigner, final String identifier) { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java index 2c459976c..e48cc0056 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java @@ -31,11 +31,11 @@ public class Eth1SignForIdentifierHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); - private final SignerForIdentifier signerForIdentifier; + private final SignerForIdentifier signerForIdentifier; private final HttpApiMetrics metrics; public Eth1SignForIdentifierHandler( - final SignerForIdentifier signerForIdentifier, final HttpApiMetrics metrics) { + final SignerForIdentifier signerForIdentifier, final HttpApiMetrics metrics) { this.signerForIdentifier = signerForIdentifier; this.metrics = metrics; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SigningExtensionHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SigningExtensionHandler.java index be9100a2a..596d15e5d 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SigningExtensionHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SigningExtensionHandler.java @@ -36,9 +36,9 @@ public class SigningExtensionHandler implements Handler { .copy() .enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - private final SignerForIdentifier signerForIdentifier; + private final SignerForIdentifier signerForIdentifier; - public SigningExtensionHandler(final SignerForIdentifier signerForIdentifier) { + public SigningExtensionHandler(final SignerForIdentifier signerForIdentifier) { this.signerForIdentifier = signerForIdentifier; } 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 3e8a7a974..4298dfb0a 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 @@ -56,7 +56,7 @@ public class Eth2SignForIdentifierHandler implements Handler { private static final Logger LOG = LogManager.getLogger(); - private final SignerForIdentifier signerForIdentifier; + private final SignerForIdentifier signerForIdentifier; private final HttpApiMetrics httpMetrics; private final SlashingProtectionMetrics slashingMetrics; private final Optional slashingProtection; @@ -69,7 +69,7 @@ public class Eth2SignForIdentifierHandler implements Handler { public static final int SLASHING_PROTECTION_ENFORCED = 412; public Eth2SignForIdentifierHandler( - final SignerForIdentifier signerForIdentifier, + final SignerForIdentifier signerForIdentifier, final HttpApiMetrics httpMetrics, final SlashingProtectionMetrics slashingMetrics, final Optional slashingProtection, diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java index 80701b0f0..2c62bdd27 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java @@ -33,10 +33,10 @@ public class EthSignResultProvider implements ResultProvider { private static final Logger LOG = LogManager.getLogger(); - private final SignerForIdentifier transactionSignerProvider; + private final SignerForIdentifier transactionSignerProvider; public EthSignResultProvider( - final SignerForIdentifier transactionSignerProvider) { + final SignerForIdentifier transactionSignerProvider) { this.transactionSignerProvider = transactionSignerProvider; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java index 2b4b0f700..ba25985aa 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java @@ -40,11 +40,11 @@ public class EthSignTransactionResultProvider implements ResultProvider private final long chainId; private final JsonDecoder decoder; - private final SignerForIdentifier secpSigner; + private final SignerForIdentifier secpSigner; public EthSignTransactionResultProvider( final long chainId, - final SignerForIdentifier secpSigner, + final SignerForIdentifier secpSigner, final JsonDecoder decoder) { this.chainId = chainId; this.decoder = decoder; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java index e3f6be396..430d73553 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java @@ -34,10 +34,10 @@ public class EthSignTypedDataResultProvider implements ResultProvider { private static final Logger LOG = LogManager.getLogger(); - private final SignerForIdentifier transactionSignerProvider; + private final SignerForIdentifier transactionSignerProvider; public EthSignTypedDataResultProvider( - final SignerForIdentifier transactionSignerProvider) { + final SignerForIdentifier transactionSignerProvider) { this.transactionSignerProvider = transactionSignerProvider; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java index a090f67a0..557892a2f 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java @@ -41,14 +41,14 @@ public class SendTransactionHandler implements JsonRpcRequestHandler { private final TransactionFactory transactionFactory; private final VertxRequestTransmitterFactory vertxTransmitterFactory; - private final SignerForIdentifier secpSigner; + private final SignerForIdentifier secpSigner; private static final int MAX_NONCE_RETRIES = 10; public SendTransactionHandler( final long chainId, final TransactionFactory transactionFactory, final VertxRequestTransmitterFactory vertxTransmitterFactory, - final SignerForIdentifier secpSigner) { + final SignerForIdentifier secpSigner) { this.chainId = chainId; this.transactionFactory = transactionFactory; this.vertxTransmitterFactory = vertxTransmitterFactory; @@ -89,7 +89,7 @@ public void handle(final RoutingContext context, final JsonRpcRequest request) { private void sendTransaction( final Transaction transaction, final RoutingContext routingContext, - final SignerForIdentifier secpSigner, + final SignerForIdentifier secpSigner, final JsonRpcRequest request) { final TransactionSerializer transactionSerializer = diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java index 233fad7f9..186a17b45 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/signing/TransactionSerializer.java @@ -32,11 +32,10 @@ public class TransactionSerializer { - protected final SignerForIdentifier secpSigner; + protected final SignerForIdentifier secpSigner; protected final long chainId; - public TransactionSerializer( - final SignerForIdentifier secpSigner, final long chainId) { + public TransactionSerializer(final SignerForIdentifier secpSigner, final long chainId) { this.secpSigner = secpSigner; this.chainId = chainId; } @@ -79,9 +78,11 @@ private static byte[] prependEip1559TransactionType(byte[] bytesToSign) { private SignatureData sign(final String eth1Address, final byte[] bytesToSign) { final SecpArtifactSignature artifactSignature = - secpSigner - .signAndGetArtifactSignature(normaliseIdentifier(eth1Address), Bytes.of(bytesToSign)) - .orElseThrow(() -> new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT)); + (SecpArtifactSignature) + secpSigner + .signAndGetArtifactSignature( + normaliseIdentifier(eth1Address), Bytes.of(bytesToSign)) + .orElseThrow(() -> new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT)); final Signature signature = artifactSignature.getSignatureData(); diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java index 1d03dfa72..ff1214f49 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSigningRootTest.java @@ -155,7 +155,7 @@ void computeSigningRootForBLSProxyKey(final Eth2Network network) { // verify BLS Signature matching Commit Boost client implementation as well final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(DELEGATOR_KEY_PAIR, null); - final String signature = artifactSigner.sign(signingRoot).getSignatureData().toString(); + final String signature = artifactSigner.sign(signingRoot).asHex(); assertThat(signature).isEqualTo(BLS_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } @@ -178,7 +178,7 @@ void computeSigningRootforSECPProxyKey(final Eth2Network network) { // verify BLS Signature matching Commit Boost client implementation as well final BlsArtifactSigner artifactSigner = new BlsArtifactSigner(DELEGATOR_KEY_PAIR, null); BlsArtifactSignature blsArtifactSignature = artifactSigner.sign(signingRoot); - String signature = blsArtifactSignature.getSignatureData().toString(); + String signature = blsArtifactSignature.asHex(); assertThat(signature).isEqualTo(SECP_PROXY_MESSAGE_SIGNATURE_MAP.get(network)); } diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignResultProviderTest.java index 78dbe5bcc..77ae13f18 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignResultProviderTest.java @@ -55,7 +55,7 @@ @ExtendWith(MockitoExtension.class) public class EthSignResultProviderTest { - @Mock SignerForIdentifier transactionSignerProvider; + @Mock SignerForIdentifier transactionSignerProvider; @ParameterizedTest @ArgumentsSource(InvalidParamsProvider.class) @@ -97,7 +97,7 @@ public void signatureHasTheExpectedFormat() { final SecpArtifactSignature secpArtifactSignature = new SecpArtifactSignature(new Signature(v, r, s)); - doReturn(Optional.of(SecpArtifactSignature.toBytes(secpArtifactSignature).toHexString())) + doReturn(Optional.of(secpArtifactSignature.asHex())) .when(transactionSignerProvider) .sign(any(), any(Bytes.class)); @@ -138,13 +138,12 @@ public void returnsExpectedSignature(final String message) { Bytes data = answer.getArgument(1, Bytes.class); final Sign.SignatureData signature = Sign.signMessage(data.toArrayUnsafe(), keyPair); return Optional.of( - SecpArtifactSignature.toBytes( - new SecpArtifactSignature( - new Signature( - new BigInteger(signature.getV()), - new BigInteger(1, signature.getR()), - new BigInteger(1, signature.getS())))) - .toHexString()); + new SecpArtifactSignature( + new Signature( + new BigInteger(signature.getV()), + new BigInteger(1, signature.getR()), + new BigInteger(1, signature.getS()))) + .asHex()); }) .when(transactionSignerProvider) .sign(anyString(), any(Bytes.class)); diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTypedDataResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTypedDataResultProviderTest.java index a73a90139..65fe11e1a 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTypedDataResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTypedDataResultProviderTest.java @@ -71,7 +71,7 @@ public class EthSignTypedDataResultProviderTest { private static final ECKeyPair KEY_PAIR = new ECKeyPair(PRIVATE_KEY, PUBLIC_KEY); - @Mock SignerForIdentifier transactionSignerProvider; + @Mock SignerForIdentifier transactionSignerProvider; @ParameterizedTest @ArgumentsSource(InvalidParamsProvider.class) @@ -147,12 +147,11 @@ public Stream provideArguments(final ExtensionContext conte } private String hexFromSignatureData(Sign.SignatureData signature) { - return SecpArtifactSignature.toBytes( - new SecpArtifactSignature( - new Signature( - new BigInteger(signature.getV()), - new BigInteger(1, signature.getR()), - new BigInteger(1, signature.getS())))) - .toHexString(); + return new SecpArtifactSignature( + new Signature( + new BigInteger(signature.getV()), + new BigInteger(1, signature.getR()), + new BigInteger(1, signature.getS()))) + .asHex(); } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java index 734026f65..1aec75556 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/BlsArtifactSignature.java @@ -30,6 +30,7 @@ public BLSSignature getSignatureData() { return blsSignature; } + @Override public String asHex() { return blsSignature.toString(); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java index 6c1d6916f..667291917 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java @@ -54,13 +54,4 @@ public static SecpArtifactSignature fromBytes(final Bytes signature) { Numeric.toBigInt(r.toArrayUnsafe()), Numeric.toBigInt(s.toArrayUnsafe()))); } - - @Deprecated // use asHex instead - public static Bytes toBytes(final SecpArtifactSignature signature) { - final Signature signatureData = signature.getSignatureData(); - return Bytes.concatenate( - Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signatureData.getR()))), - Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signatureData.getS()))), - Bytes.wrap(ByteUtils.bigIntegerToBytes(signatureData.getV()))); - } } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignatureTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignatureTest.java index f76bd0901..9f81f379d 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignatureTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignatureTest.java @@ -28,7 +28,7 @@ void hexEncodedSignatureIsReturned() { final BLSSignature blsSignature = BLSSignature.fromBytesCompressed(Bytes.fromHexString(SIGNATURE)); final BlsArtifactSignature blsArtifactSignature = new BlsArtifactSignature(blsSignature); - assertThat(blsArtifactSignature.getSignatureData().toString()).isEqualTo(SIGNATURE); + assertThat(blsArtifactSignature.asHex()).isEqualTo(SIGNATURE); assertThat(blsSignature.toBytesCompressed().toHexString()).isEqualTo(SIGNATURE); assertThat(blsSignature.toString()).isEqualTo(SIGNATURE); } diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignerTest.java index 0abfa331b..3919fc0a4 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/BlsArtifactSignerTest.java @@ -45,6 +45,6 @@ void signsData() { new BlsArtifactSigner(keyPair, SignerOrigin.FILE_RAW); final BlsArtifactSignature signature = blsArtifactSigner.sign(message); - assertThat(signature.getSignatureData().toString()).isEqualTo(expectedSignature.toString()); + assertThat(signature.asHex()).isEqualTo(expectedSignature.toString()); } } From b8aa52d298b49238fc27363473e34f79ec8d5fd4 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 15:17:05 +1000 Subject: [PATCH 49/57] simplifying code --- .../CommitBoostGenerateProxyKeyHandler.java | 2 -- .../signing/Eth1SignForIdentifierHandler.java | 14 ++++++-- .../handlers/signing/SignerForIdentifier.java | 32 ++++--------------- .../EthSignResultProvider.java | 4 +-- .../EthSignTransactionResultProvider.java | 5 +-- .../EthSignTypedDataResultProvider.java | 4 +-- .../SendTransactionHandler.java | 1 - .../EthSignTransactionResultProviderTest.java | 2 +- 8 files changed, 22 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index c21787213..c6d81952d 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -32,8 +32,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.vertx.core.Handler; import io.vertx.ext.web.RoutingContext; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; public class CommitBoostGenerateProxyKeyHandler implements Handler { diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java index e48cc0056..becfac852 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/Eth1SignForIdentifierHandler.java @@ -14,7 +14,6 @@ import static io.vertx.core.http.HttpHeaders.CONTENT_TYPE; import static tech.pegasys.web3signer.core.service.http.handlers.ContentTypes.TEXT_PLAIN_UTF_8; -import static tech.pegasys.web3signer.core.service.http.handlers.signing.SignerForIdentifier.toBytes; import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier; import tech.pegasys.web3signer.core.service.http.metrics.HttpApiMetrics; @@ -23,6 +22,7 @@ import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RequestBody; import io.vertx.ext.web.RoutingContext; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -47,7 +47,7 @@ public void handle(final RoutingContext routingContext) { final Bytes data; try { data = getDataToSign(routingContext.body()); - } catch (final IllegalArgumentException e) { + } catch (final RuntimeException e) { metrics.getMalformedRequestCounter().inc(); LOG.debug("Invalid signing request", e); routingContext.fail(400); @@ -72,6 +72,14 @@ private void respondWithSignature(final RoutingContext routingContext, final Str private Bytes getDataToSign(final RequestBody requestBody) { final JsonObject jsonObject = requestBody.asJsonObject(); - return toBytes(jsonObject.getString("data")); + + if (!jsonObject.containsKey("data")) { + throw new IllegalArgumentException("Request must contain a 'data' field"); + } + if (StringUtils.isBlank(jsonObject.getString("data"))) { + throw new IllegalArgumentException("Data field must not be empty"); + } + + return Bytes.fromHexString(jsonObject.getString("data")); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java index 745bd2d3a..d2b717451 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/signing/SignerForIdentifier.java @@ -17,9 +17,6 @@ import java.util.Optional; -import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; /** @@ -27,7 +24,6 @@ * available for a given identifier and to sign a message. */ public class SignerForIdentifier { - private static final Logger LOG = LogManager.getLogger(); private final ArtifactSignerProvider signerProvider; public SignerForIdentifier(final ArtifactSignerProvider signerProvider) { @@ -47,30 +43,16 @@ public Optional sign(final String identifier, final Bytes data) { return signerProvider.getSigner(identifier).map(signer -> signer.sign(data).asHex()); } - public Optional signAndGetArtifactSignature( - final String identifier, final Bytes data) { - return signerProvider.getSigner(identifier).map(signer -> signer.sign(data)); - } - /** - * Converts hex string to bytes + * Sign data for given identifier and return ArtifactSignature. Useful for SECP signing. * - * @param data hex string - * @return Bytes - * @throws IllegalArgumentException if data is invalid i.e. not a valid hex string, null or empty + * @param identifier The identifier for which to sign data. + * @param data Bytes which is signed + * @return Optional ArtifactSignature. Empty if no signer available for given identifier */ - public static Bytes toBytes(final String data) { - final Bytes dataToSign; - try { - if (StringUtils.isBlank(data)) { - throw new IllegalArgumentException("Blank data"); - } - dataToSign = Bytes.fromHexString(data); - } catch (final IllegalArgumentException e) { - LOG.debug("Invalid hex string {}", data, e); - throw e; - } - return dataToSign; + public Optional signAndGetArtifactSignature( + final String identifier, final Bytes data) { + return signerProvider.getSigner(identifier).map(signer -> signer.sign(data)); } /** diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java index 2c62bdd27..f14b26e5b 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignResultProvider.java @@ -21,7 +21,6 @@ import tech.pegasys.web3signer.core.service.jsonrpc.JsonRpcRequest; import tech.pegasys.web3signer.core.service.jsonrpc.exceptions.JsonRpcException; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.ResultProvider; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import java.util.List; @@ -35,8 +34,7 @@ public class EthSignResultProvider implements ResultProvider { private final SignerForIdentifier transactionSignerProvider; - public EthSignResultProvider( - final SignerForIdentifier transactionSignerProvider) { + public EthSignResultProvider(final SignerForIdentifier transactionSignerProvider) { this.transactionSignerProvider = transactionSignerProvider; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java index ba25985aa..8043644fc 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTransactionResultProvider.java @@ -25,7 +25,6 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.EthTransaction; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.Transaction; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.signing.TransactionSerializer; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import java.util.List; @@ -43,9 +42,7 @@ public class EthSignTransactionResultProvider implements ResultProvider private final SignerForIdentifier secpSigner; public EthSignTransactionResultProvider( - final long chainId, - final SignerForIdentifier secpSigner, - final JsonDecoder decoder) { + final long chainId, final SignerForIdentifier secpSigner, final JsonDecoder decoder) { this.chainId = chainId; this.decoder = decoder; this.secpSigner = secpSigner; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java index 430d73553..a7e1d2cf4 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/internalresponse/EthSignTypedDataResultProvider.java @@ -20,7 +20,6 @@ import tech.pegasys.web3signer.core.service.jsonrpc.JsonRpcRequest; import tech.pegasys.web3signer.core.service.jsonrpc.exceptions.JsonRpcException; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.ResultProvider; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import java.io.IOException; import java.util.List; @@ -36,8 +35,7 @@ public class EthSignTypedDataResultProvider implements ResultProvider { private final SignerForIdentifier transactionSignerProvider; - public EthSignTypedDataResultProvider( - final SignerForIdentifier transactionSignerProvider) { + public EthSignTypedDataResultProvider(final SignerForIdentifier transactionSignerProvider) { this.transactionSignerProvider = transactionSignerProvider; } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java index 557892a2f..2aedfda5b 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/jsonrpc/handlers/sendtransaction/SendTransactionHandler.java @@ -26,7 +26,6 @@ import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.Transaction; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.sendtransaction.transaction.TransactionFactory; import tech.pegasys.web3signer.core.service.jsonrpc.handlers.signing.TransactionSerializer; -import tech.pegasys.web3signer.signing.SecpArtifactSignature; import io.vertx.core.json.DecodeException; import io.vertx.ext.web.RoutingContext; diff --git a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java index 59ddc1016..4e37487bf 100644 --- a/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java +++ b/core/src/test/java/tech/pegasys/web3signer/core/service/jsonrpc/EthSignTransactionResultProviderTest.java @@ -61,7 +61,7 @@ public class EthSignTransactionResultProviderTest { private static JsonDecoder jsonDecoder; private static long chainId; - @Mock SignerForIdentifier mockSignerForIdentifier; + @Mock SignerForIdentifier mockSignerForIdentifier; @BeforeAll static void beforeAll() { From 6dd046b1da4087eee4b472db2e4f646f15a76c38 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 7 Nov 2024 15:36:56 +1000 Subject: [PATCH 50/57] illegal state exception --- .../core/routes/eth2/CommitBoostRequestSignatureRoute.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 index 3241db2a6..820040851 100644 --- 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 @@ -43,7 +43,10 @@ public CommitBoostRequestSignatureRoute( context.getArtifactSignerProviders().stream() .filter(p -> p instanceof DefaultArtifactSignerProvider) .findFirst() - .orElseThrow(); + .orElseThrow( + () -> + new IllegalStateException( + "No DefaultArtifactSignerProvider found in Context for eth2 mode")); } @Override From 0cce568c5d818418d7a0f7989e64b3ca37e5fdb6 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Sun, 10 Nov 2024 15:09:20 +0700 Subject: [PATCH 51/57] Introduce K256ArtifactSigner that generates R+S signature --- .../signing/K256ArtifactSigner.java | 170 ++++++++++++++++++ .../signing/SecpArtifactSignature.java | 7 + .../signing/secp256k1/EthPublicKeyUtils.java | 15 ++ .../signing/K256ArtifactSignerTest.java | 43 +++++ 4 files changed, 235 insertions(+) create mode 100644 signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java create mode 100644 signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java new file mode 100644 index 000000000..00337b4bb --- /dev/null +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java @@ -0,0 +1,170 @@ +/* + * 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.signing; + +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import tech.pegasys.web3signer.signing.util.IdentifierUtils; + +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Objects; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.tuweni.bytes.Bytes; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.ECDomainParameters; +import org.bouncycastle.crypto.params.ECPrivateKeyParameters; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.jce.ECNamedCurveTable; +import org.bouncycastle.jce.spec.ECParameterSpec; +import org.bouncycastle.math.ec.ECPoint; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Sign; + +/** + * An artifact signer for SECP256K1 keys used specifically for Commit Boost API ECDSA proxy keys. + */ +public class K256ArtifactSigner implements ArtifactSigner { + private final ECKeyPair ecKeyPair; + private static final BigInteger CURVE_ORDER = + new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); + private static final BigInteger HALF_CURVE_ORDER = CURVE_ORDER.shiftRight(1); + private static final ECParameterSpec EC_SPEC = ECNamedCurveTable.getParameterSpec("secp256k1"); + private static final ECDomainParameters DOMAIN_PARAMETERS = + new ECDomainParameters( + EC_SPEC.getCurve(), EC_SPEC.getG(), EC_SPEC.getN(), EC_SPEC.getH(), EC_SPEC.getSeed()); + + public K256ArtifactSigner(final ECKeyPair web3JECKeypair) { + this.ecKeyPair = web3JECKeypair; + } + + @Override + public String getIdentifier() { + final String hexString = + EthPublicKeyUtils.getEncoded( + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()), true) + .toHexString(); + return IdentifierUtils.normaliseIdentifier(hexString); + } + + @Override + public ArtifactSignature sign(final Bytes message) { + try { + + // Use BouncyCastle's ECDSASigner with HMacDSAKCalculator for deterministic ECDSA + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + ECPrivateKeyParameters privKey = + new ECPrivateKeyParameters(ecKeyPair.getPrivateKey(), DOMAIN_PARAMETERS); + signer.init(true, privKey); + BigInteger[] components = signer.generateSignature(message.toArrayUnsafe()); + + // Canonicalize the signature + BigInteger r = components[0]; + BigInteger s = components[1]; + if (s.compareTo(HALF_CURVE_ORDER) > 0) { + s = CURVE_ORDER.subtract(s); + } + + // Ensure r and s are 32 bytes each + byte[] rBytes = ensureLength(r.toByteArray(), 32); + byte[] sBytes = ensureLength(s.toByteArray(), 32); + + // Concatenate r and s + byte[] concatenated = new byte[64]; + System.arraycopy(rBytes, 0, concatenated, 0, 32); + System.arraycopy(sBytes, 0, concatenated, 32, 32); + + return new K256ArtifactSignature(concatenated); + } catch (Exception e) { + throw new RuntimeException("Error signing message", e); + } + } + + @VisibleForTesting + public boolean verify(final Bytes message, final ArtifactSignature signature) { + try { + byte[] concatenated = Bytes.fromHexString(signature.asHex()).toArray(); + byte[] rBytes = Arrays.copyOfRange(concatenated, 0, 32); + byte[] sBytes = Arrays.copyOfRange(concatenated, 32, 64); + + BigInteger r = new BigInteger(1, rBytes); + BigInteger s = new BigInteger(1, sBytes); + + ECPoint pubECPoint = Sign.publicPointFromPrivate(ecKeyPair.getPrivateKey()); + ECPublicKeyParameters ecPublicKeyParameters = + new ECPublicKeyParameters(pubECPoint, DOMAIN_PARAMETERS); + + // Use BouncyCastle's ECDSASigner with HMacDSAKCalculator for deterministic ECDSA + ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + signer.init(false, ecPublicKeyParameters); + return signer.verifySignature(message.toArray(), r, s); + } catch (Exception e) { + throw new RuntimeException("Error verifying signature", e); + } + } + + @Override + public KeyType getKeyType() { + return KeyType.SECP256K1; + } + + private static byte[] ensureLength(final byte[] array, final int length) { + if (array.length == length) { + return array; + } else if (array.length > length) { + return Arrays.copyOfRange(array, array.length - length, array.length); + } else { + byte[] padded = new byte[length]; + System.arraycopy(array, 0, padded, length - array.length, array.length); + return padded; + } + } + + private static class K256ArtifactSignature implements ArtifactSignature { + final Bytes signature; + + public K256ArtifactSignature(final byte[] signature) { + this.signature = Bytes.of(signature); + } + + @Override + public KeyType getType() { + return KeyType.SECP256K1; + } + + @Override + public String asHex() { + return signature.toHexString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + K256ArtifactSignature that = (K256ArtifactSignature) o; + return Objects.equals(signature, that.signature); + } + + @Override + public int hashCode() { + return Objects.hashCode(signature); + } + + @Override + public String toString() { + return signature.toHexString(); + } + } +} diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java index 667291917..be7ad9717 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java @@ -44,6 +44,13 @@ public String asHex() { .toHexString(); } + public String asEncodedHex() { + return Bytes.concatenate( + Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getR()))), + Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getS())))) + .toHexString(); + } + public static SecpArtifactSignature fromBytes(final Bytes signature) { final Bytes r = signature.slice(0, 32); final Bytes s = signature.slice(32, 32); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 6a143252f..074987554 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -18,9 +18,11 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECPrivateKeySpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; @@ -34,6 +36,7 @@ import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; +import org.web3j.crypto.ECKeyPair; public class EthPublicKeyUtils { private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider(); @@ -84,6 +87,18 @@ public static KeyPair createSecp256k1KeyPair(final SecureRandom random) return keyPairGenerator.generateKeyPair(); } + public static KeyPair web3JECKeypairToJavaKeyPair(final ECKeyPair web3JECKeypair) { + try { + PrivateKey ecPrivateKey = + KeyFactory.getInstance("EC", BC_PROVIDER) + .generatePrivate( + new ECPrivateKeySpec(web3JECKeypair.getPrivateKey(), JAVA_SECP256K1_SPEC)); + return new KeyPair(bigIntegerToECPublicKey(web3JECKeypair.getPublicKey()), ecPrivateKey); + } catch (final Exception e) { + throw new RuntimeException("Unable to convert web3j to Java EC keypair", e); + } + } + /** * Convert a public key in bytes format to an ECPublicKey. * diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java new file mode 100644 index 000000000..107e3e1e8 --- /dev/null +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java @@ -0,0 +1,43 @@ +/* + * 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.signing; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; +import org.web3j.crypto.ECKeyPair; +import org.web3j.utils.Numeric; + +class K256ArtifactSignerTest { + private static final String PRIVATE_KEY_HEX = + "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"; + private static final String OBJECT_ROOT_HEX = + "419a4f6b748659b3ac4fc3534f3767fffe78127d210af0b2e1c1c8e7b345cf64"; + + @Test + void signCreatesVerifiableSignature() { + // generate signature using web3j + ECKeyPair web3jECKeyPair = ECKeyPair.create(Numeric.toBigInt(PRIVATE_KEY_HEX)); + Bytes messageToSign = Bytes.fromHexString(OBJECT_ROOT_HEX); + + // generate using K256ArtifactSigner + K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(web3jECKeyPair); + System.out.println("K256ArtifactSigner Signature (using Bouncycastle:"); + ArtifactSignature artifactSignature = k256ArtifactSigner.sign(messageToSign); + System.out.println("R+S: " + artifactSignature.asHex()); + + // Verify the signature against public key + assertThat(k256ArtifactSigner.verify(messageToSign, artifactSignature)).isTrue(); + } +} From 45da58b78067c161b240a275df5fb76671310e84 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 11 Nov 2024 15:17:18 +0700 Subject: [PATCH 52/57] K256ArtifactSigner that matches Rust K-256 signature generation --- .../signing/K256ArtifactSigner.java | 84 +++++++++---------- .../signing/K256ArtifactSignerTest.java | 16 ++-- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java index 00337b4bb..c27b955d8 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java @@ -16,20 +16,25 @@ import tech.pegasys.web3signer.signing.util.IdentifierUtils; import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Objects; import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.MutableBytes; +import org.apache.tuweni.units.bigints.UInt256; +import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.HMacDSAKCalculator; -import org.bouncycastle.jce.ECNamedCurveTable; -import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.math.ec.ECPoint; +import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Sign; @@ -38,13 +43,11 @@ */ public class K256ArtifactSigner implements ArtifactSigner { private final ECKeyPair ecKeyPair; - private static final BigInteger CURVE_ORDER = - new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16); - private static final BigInteger HALF_CURVE_ORDER = CURVE_ORDER.shiftRight(1); - private static final ECParameterSpec EC_SPEC = ECNamedCurveTable.getParameterSpec("secp256k1"); - private static final ECDomainParameters DOMAIN_PARAMETERS = + public static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1"); + static final ECDomainParameters CURVE = new ECDomainParameters( - EC_SPEC.getCurve(), EC_SPEC.getG(), EC_SPEC.getN(), EC_SPEC.getH(), EC_SPEC.getSeed()); + CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); + static final BigInteger HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1); public K256ArtifactSigner(final ECKeyPair web3JECKeypair) { this.ecKeyPair = web3JECKeypair; @@ -62,32 +65,25 @@ public String getIdentifier() { @Override public ArtifactSignature sign(final Bytes message) { try { - // Use BouncyCastle's ECDSASigner with HMacDSAKCalculator for deterministic ECDSA - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); - ECPrivateKeyParameters privKey = - new ECPrivateKeyParameters(ecKeyPair.getPrivateKey(), DOMAIN_PARAMETERS); + final ECPrivateKeyParameters privKey = + new ECPrivateKeyParameters(ecKeyPair.getPrivateKey(), CURVE); + final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); signer.init(true, privKey); - BigInteger[] components = signer.generateSignature(message.toArrayUnsafe()); - // Canonicalize the signature - BigInteger r = components[0]; - BigInteger s = components[1]; - if (s.compareTo(HALF_CURVE_ORDER) > 0) { - s = CURVE_ORDER.subtract(s); - } + // apply sha256 digest to the message before sending it to signing + final BigInteger[] components = signer.generateSignature(calculateSHA256(message.toArray())); - // Ensure r and s are 32 bytes each - byte[] rBytes = ensureLength(r.toByteArray(), 32); - byte[] sBytes = ensureLength(s.toByteArray(), 32); + // create a canonicalised compact signature (R+S) + final ECDSASignature signature = + new ECDSASignature(components[0], components[1]).toCanonicalised(); - // Concatenate r and s - byte[] concatenated = new byte[64]; - System.arraycopy(rBytes, 0, concatenated, 0, 32); - System.arraycopy(sBytes, 0, concatenated, 32, 32); + final MutableBytes concatenated = MutableBytes.create(64); + UInt256.valueOf(signature.r).copyTo(concatenated, 0); + UInt256.valueOf(signature.s).copyTo(concatenated, 32); - return new K256ArtifactSignature(concatenated); - } catch (Exception e) { + return new K256ArtifactSignature(concatenated.toArray()); + } catch (final Exception e) { throw new RuntimeException("Error signing message", e); } } @@ -95,6 +91,7 @@ public ArtifactSignature sign(final Bytes message) { @VisibleForTesting public boolean verify(final Bytes message, final ArtifactSignature signature) { try { + // we are assuming that we got 64 bytes signature in R+S format byte[] concatenated = Bytes.fromHexString(signature.asHex()).toArray(); byte[] rBytes = Arrays.copyOfRange(concatenated, 0, 32); byte[] sBytes = Arrays.copyOfRange(concatenated, 32, 64); @@ -102,14 +99,15 @@ public boolean verify(final Bytes message, final ArtifactSignature signature) { BigInteger r = new BigInteger(1, rBytes); BigInteger s = new BigInteger(1, sBytes); - ECPoint pubECPoint = Sign.publicPointFromPrivate(ecKeyPair.getPrivateKey()); - ECPublicKeyParameters ecPublicKeyParameters = - new ECPublicKeyParameters(pubECPoint, DOMAIN_PARAMETERS); + final ECPoint pubECPoint = Sign.publicPointFromPrivate(ecKeyPair.getPrivateKey()); + final ECPublicKeyParameters ecPublicKeyParameters = + new ECPublicKeyParameters(pubECPoint, CURVE); // Use BouncyCastle's ECDSASigner with HMacDSAKCalculator for deterministic ECDSA - ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); signer.init(false, ecPublicKeyParameters); - return signer.verifySignature(message.toArray(), r, s); + // apply sha-256 before verification + return signer.verifySignature(calculateSHA256(message.toArray()), r, s); } catch (Exception e) { throw new RuntimeException("Error verifying signature", e); } @@ -120,19 +118,19 @@ public KeyType getKeyType() { return KeyType.SECP256K1; } - private static byte[] ensureLength(final byte[] array, final int length) { - if (array.length == length) { - return array; - } else if (array.length > length) { - return Arrays.copyOfRange(array, array.length - length, array.length); - } else { - byte[] padded = new byte[length]; - System.arraycopy(array, 0, padded, length - array.length, array.length); - return padded; + public static byte[] calculateSHA256(byte[] message) { + try { + // Create a MessageDigest instance for SHA-256 + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + // Update the MessageDigest with the message bytes + return digest.digest(message); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not found", e); } } - private static class K256ArtifactSignature implements ArtifactSignature { + public static class K256ArtifactSignature implements ArtifactSignature { final Bytes signature; public K256ArtifactSignature(final byte[] signature) { diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java index 107e3e1e8..ab2391749 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java @@ -28,16 +28,20 @@ class K256ArtifactSignerTest { @Test void signCreatesVerifiableSignature() { // generate signature using web3j - ECKeyPair web3jECKeyPair = ECKeyPair.create(Numeric.toBigInt(PRIVATE_KEY_HEX)); - Bytes messageToSign = Bytes.fromHexString(OBJECT_ROOT_HEX); + final ECKeyPair web3jECKeyPair = ECKeyPair.create(Numeric.toBigInt(PRIVATE_KEY_HEX)); + final Bytes messageToSign = Bytes.fromHexString(OBJECT_ROOT_HEX); // generate using K256ArtifactSigner - K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(web3jECKeyPair); - System.out.println("K256ArtifactSigner Signature (using Bouncycastle:"); - ArtifactSignature artifactSignature = k256ArtifactSigner.sign(messageToSign); - System.out.println("R+S: " + artifactSignature.asHex()); + final K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(web3jECKeyPair); + final ArtifactSignature artifactSignature = k256ArtifactSigner.sign(messageToSign); // Verify the signature against public key assertThat(k256ArtifactSigner.verify(messageToSign, artifactSignature)).isTrue(); + + // copied from Rust K-256 and Python ecdsa module + final Bytes expectedSignature = + Bytes.fromHexString( + "8C32902BE980399CA59FCC222CCF0A5FE355A159122DEA58789A3938E29D89797FC6C9C0ECCCD29705915729F5326BB7D245F8E54D3A793A06DE3C92ABA85057"); + assertThat(Bytes.fromHexString(artifactSignature.asHex())).isEqualTo(expectedSignature); } } From 53f2c594c1f19a10cc93cf5e321560da4c9f6ca9 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 11 Nov 2024 17:02:06 +0700 Subject: [PATCH 53/57] K256ArtifactSigner that matches Rust K-256 signature generation --- .../signing/K256ArtifactSigner.java | 40 +++------------ .../signing/K256ArtifactSignerTest.java | 49 +++++++++++++++---- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java index c27b955d8..c416acb69 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/K256ArtifactSigner.java @@ -18,10 +18,8 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Arrays; import java.util.Objects; -import com.google.common.annotations.VisibleForTesting; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.MutableBytes; import org.apache.tuweni.units.bigints.UInt256; @@ -30,24 +28,22 @@ import org.bouncycastle.crypto.ec.CustomNamedCurves; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.crypto.params.ECPrivateKeyParameters; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; import org.bouncycastle.crypto.signers.ECDSASigner; import org.bouncycastle.crypto.signers.HMacDSAKCalculator; -import org.bouncycastle.math.ec.ECPoint; import org.web3j.crypto.ECDSASignature; import org.web3j.crypto.ECKeyPair; -import org.web3j.crypto.Sign; /** - * An artifact signer for SECP256K1 keys used specifically for Commit Boost API ECDSA proxy keys. + * An artifact signer for SECP256K1 keys used specifically for Commit Boost API ECDSA proxy keys. It + * uses compressed public key as identifier and signs the message with just sha256 digest. The + * signature complies with RFC-6979. */ public class K256ArtifactSigner implements ArtifactSigner { private final ECKeyPair ecKeyPair; public static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1"); - static final ECDomainParameters CURVE = + public static final ECDomainParameters CURVE = new ECDomainParameters( CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(), CURVE_PARAMS.getH()); - static final BigInteger HALF_CURVE_ORDER = CURVE_PARAMS.getN().shiftRight(1); public K256ArtifactSigner(final ECKeyPair web3JECKeypair) { this.ecKeyPair = web3JECKeypair; @@ -74,10 +70,11 @@ public ArtifactSignature sign(final Bytes message) { // apply sha256 digest to the message before sending it to signing final BigInteger[] components = signer.generateSignature(calculateSHA256(message.toArray())); - // create a canonicalised compact signature (R+S) + // create a canonicalised signature using Web3J ECDSASignature class final ECDSASignature signature = new ECDSASignature(components[0], components[1]).toCanonicalised(); + // convert to compact signature format final MutableBytes concatenated = MutableBytes.create(64); UInt256.valueOf(signature.r).copyTo(concatenated, 0); UInt256.valueOf(signature.s).copyTo(concatenated, 32); @@ -88,31 +85,6 @@ public ArtifactSignature sign(final Bytes message) { } } - @VisibleForTesting - public boolean verify(final Bytes message, final ArtifactSignature signature) { - try { - // we are assuming that we got 64 bytes signature in R+S format - byte[] concatenated = Bytes.fromHexString(signature.asHex()).toArray(); - byte[] rBytes = Arrays.copyOfRange(concatenated, 0, 32); - byte[] sBytes = Arrays.copyOfRange(concatenated, 32, 64); - - BigInteger r = new BigInteger(1, rBytes); - BigInteger s = new BigInteger(1, sBytes); - - final ECPoint pubECPoint = Sign.publicPointFromPrivate(ecKeyPair.getPrivateKey()); - final ECPublicKeyParameters ecPublicKeyParameters = - new ECPublicKeyParameters(pubECPoint, CURVE); - - // Use BouncyCastle's ECDSASigner with HMacDSAKCalculator for deterministic ECDSA - final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); - signer.init(false, ecPublicKeyParameters); - // apply sha-256 before verification - return signer.verifySignature(calculateSHA256(message.toArray()), r, s); - } catch (Exception e) { - throw new RuntimeException("Error verifying signature", e); - } - } - @Override public KeyType getKeyType() { return KeyType.SECP256K1; diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java index ab2391749..dc3356e4a 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java @@ -13,30 +13,38 @@ package tech.pegasys.web3signer.signing; import static org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.web3signer.signing.K256ArtifactSigner.CURVE; +import static tech.pegasys.web3signer.signing.K256ArtifactSigner.calculateSHA256; + +import java.math.BigInteger; +import java.util.Arrays; import org.apache.tuweni.bytes.Bytes; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.math.ec.ECPoint; import org.junit.jupiter.api.Test; import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Sign; import org.web3j.utils.Numeric; class K256ArtifactSignerTest { private static final String PRIVATE_KEY_HEX = "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63"; - private static final String OBJECT_ROOT_HEX = - "419a4f6b748659b3ac4fc3534f3767fffe78127d210af0b2e1c1c8e7b345cf64"; + private static final Bytes OBJECT_ROOT = + Bytes.fromHexString("419a4f6b748659b3ac4fc3534f3767fffe78127d210af0b2e1c1c8e7b345cf64"); + private static final ECKeyPair EC_KEY_PAIR = ECKeyPair.create(Numeric.toBigInt(PRIVATE_KEY_HEX)); @Test void signCreatesVerifiableSignature() { - // generate signature using web3j - final ECKeyPair web3jECKeyPair = ECKeyPair.create(Numeric.toBigInt(PRIVATE_KEY_HEX)); - final Bytes messageToSign = Bytes.fromHexString(OBJECT_ROOT_HEX); - // generate using K256ArtifactSigner - final K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(web3jECKeyPair); - final ArtifactSignature artifactSignature = k256ArtifactSigner.sign(messageToSign); + final K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(EC_KEY_PAIR); + final ArtifactSignature artifactSignature = k256ArtifactSigner.sign(OBJECT_ROOT); // Verify the signature against public key - assertThat(k256ArtifactSigner.verify(messageToSign, artifactSignature)).isTrue(); + assertThat(verifySignature(artifactSignature)).isTrue(); // copied from Rust K-256 and Python ecdsa module final Bytes expectedSignature = @@ -44,4 +52,27 @@ void signCreatesVerifiableSignature() { "8C32902BE980399CA59FCC222CCF0A5FE355A159122DEA58789A3938E29D89797FC6C9C0ECCCD29705915729F5326BB7D245F8E54D3A793A06DE3C92ABA85057"); assertThat(Bytes.fromHexString(artifactSignature.asHex())).isEqualTo(expectedSignature); } + + boolean verifySignature(final ArtifactSignature signature) { + try { + // we are assuming that we got 64 bytes signature in R+S format + byte[] concatenated = Bytes.fromHexString(signature.asHex()).toArray(); + byte[] rBytes = Arrays.copyOfRange(concatenated, 0, 32); + byte[] sBytes = Arrays.copyOfRange(concatenated, 32, 64); + + final BigInteger r = new BigInteger(1, rBytes); + final BigInteger s = new BigInteger(1, sBytes); + + final ECPoint pubECPoint = Sign.publicPointFromPrivate(EC_KEY_PAIR.getPrivateKey()); + final ECPublicKeyParameters ecPublicKeyParameters = + new ECPublicKeyParameters(pubECPoint, CURVE); + + final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + signer.init(false, ecPublicKeyParameters); + // apply sha-256 before verification + return signer.verifySignature(calculateSHA256(OBJECT_ROOT.toArray()), r, s); + } catch (Exception e) { + throw new RuntimeException("Error verifying signature", e); + } + } } From a2a16f83a113eab6c25f449c10917d545615a4e0 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Mon, 11 Nov 2024 17:31:07 +0700 Subject: [PATCH 54/57] Use K256ArtifactSigner for commit boost api usages --- .../commitboost/ProxyKeyGenerator.java | 6 ++---- .../signing/EthSecpArtifactSigner.java | 12 ++---------- .../bulkloading/SecpV3KeystoresBulkLoader.java | 18 +++++++++++++++--- .../config/DefaultArtifactSignerProvider.java | 3 +-- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java index 4598eab86..09c49fe92 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/ProxyKeyGenerator.java @@ -25,12 +25,11 @@ import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.BlsArtifactSigner; -import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; +import tech.pegasys.web3signer.signing.K256ArtifactSigner; import tech.pegasys.web3signer.signing.KeyType; import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.metadata.SignerOrigin; import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; -import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; import java.io.IOException; import java.io.UncheckedIOException; @@ -46,7 +45,6 @@ import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes48; -import org.web3j.crypto.Credentials; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Keys; import org.web3j.crypto.Wallet; @@ -70,7 +68,7 @@ public ArtifactSigner generateECProxyKey(final String identifier) { final ECKeyPair ecKeyPair = Keys.createEcKeyPair(secureRandom); final Path ecWalletFile = createECWalletFile(ecKeyPair, identifier); LOG.debug("Created proxy EC wallet file {} for identifier: {}", ecWalletFile, identifier); - return new EthSecpArtifactSigner(new CredentialSigner(Credentials.create(ecKeyPair)), true); + return new K256ArtifactSigner(ecKeyPair); } catch (final GeneralSecurityException e) { throw new RuntimeException(e); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java index f36023730..7e572c5f5 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/EthSecpArtifactSigner.java @@ -22,23 +22,15 @@ public class EthSecpArtifactSigner implements ArtifactSigner { private final Signer signer; - private final boolean isCompressed; public EthSecpArtifactSigner(final Signer signer) { this.signer = signer; - this.isCompressed = false; - } - - public EthSecpArtifactSigner(final Signer signer, final boolean isCompressed) { - this.signer = signer; - this.isCompressed = isCompressed; } @Override public String getIdentifier() { - final String hexString = - EthPublicKeyUtils.getEncoded(signer.getPublicKey(), isCompressed).toHexString(); - return IdentifierUtils.normaliseIdentifier(hexString); + return IdentifierUtils.normaliseIdentifier( + EthPublicKeyUtils.toHexString(signer.getPublicKey())); } @Override diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java index a63fcc8b9..2c6b3fdc2 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/bulkloading/SecpV3KeystoresBulkLoader.java @@ -15,6 +15,7 @@ import tech.pegasys.web3signer.keystorage.common.MappedResults; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.EthSecpArtifactSigner; +import tech.pegasys.web3signer.signing.K256ArtifactSigner; import tech.pegasys.web3signer.signing.secp256k1.filebased.CredentialSigner; import tech.pegasys.web3signer.signing.secp256k1.util.JsonFilesUtil; @@ -39,7 +40,16 @@ public static MappedResults loadV3KeystoresUsingPasswordFileOrDi return loadV3KeystoresUsingPasswordFileOrDir(keystoresPath, pwrdFileOrDirPath, false); } - public static MappedResults loadKeystoresWithCompressedIdentifier( + /** + * Loads K256ArtifactSigners that are used in Commit Boost API. It uses compressed identifier and + * perform SHA256-SECP256K1 digest signing. + * + * @param keystoresPath Path to the directory containing the v3 keystores + * @param pwrdFileOrDirPath Path to the password file or directory containing the passwords for + * the v3 keystores + * @return MappedResults containing the loaded ArtifactSigners + */ + public static MappedResults loadECDSAProxyKeystores( final Path keystoresPath, final Path pwrdFileOrDirPath) { return loadV3KeystoresUsingPasswordFileOrDir(keystoresPath, pwrdFileOrDirPath, true); } @@ -91,8 +101,10 @@ private static MappedResults createSecpArtifactSigner( final Credentials credentials = WalletUtils.loadCredentials(password, v3KeystorePath.toFile()); - final EthSecpArtifactSigner artifactSigner = - new EthSecpArtifactSigner(new CredentialSigner(credentials), isCompressed); + final ArtifactSigner artifactSigner = + isCompressed + ? new K256ArtifactSigner(credentials.getEcKeyPair()) + : new EthSecpArtifactSigner(new CredentialSigner(credentials)); return MappedResults.newInstance(Set.of(artifactSigner), 0); } catch (final IOException | CipherException | RuntimeException e) { LOG.error("Error loading v3 keystore {}", v3KeystorePath, e); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 3567e633f..e3afa9b21 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -90,8 +90,7 @@ public Future load() { keystoreParameter, signerIdentifier, SECP256K1.name(), - SecpV3KeystoresBulkLoader - ::loadKeystoresWithCompressedIdentifier); + SecpV3KeystoresBulkLoader::loadECDSAProxyKeystores); loadProxySigners( keystoreParameter, From 8ea96fcfa6c04ab7b78b58f50a1adf753cbedd85 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 14 Nov 2024 12:15:44 +0700 Subject: [PATCH 55/57] Commit Boost Acceptance Tests List and Generate --- .../pegasys/web3signer/dsl/signer/Signer.java | 12 + .../dsl/signer/SignerConfiguration.java | 10 +- .../signer/SignerConfigurationBuilder.java | 11 +- .../runner/CmdLineParamsConfigFileImpl.java | 24 ++ .../runner/CmdLineParamsDefaultImpl.java | 18 ++ .../dsl/utils/CommitBoostATParameters.java | 60 +++++ .../CommitBoostAcceptanceTest.java | 253 ++++++++++++++++++ 7 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/CommitBoostATParameters.java create mode 100644 acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostAcceptanceTest.java 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 14acb4ac0..f6776558c 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 @@ -187,6 +187,18 @@ public Response callApiPublicKeys(final KeyType keyType) { return given().baseUri(getUrl()).get(publicKeysPath(keyType)); } + public Response callCommitBoostGetPubKeys() { + return given().baseUri(getUrl()).get("/signer/v1/get_pubkeys"); + } + + public Response callCommitBoostGenerateProxyKey(final String pubkey, final String scheme) { + return given() + .baseUri(getUrl()) + .contentType(ContentType.JSON) + .body(new JsonObject().put("pubkey", pubkey).put("scheme", scheme).toString()) + .post("/signer/v1/generate_proxy_key"); + } + public List listPublicKeys(final KeyType keyType) { return callApiPublicKeys(keyType).as(new TypeRef<>() {}); } 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 c3034d955..22b3f8535 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 @@ -18,6 +18,7 @@ import tech.pegasys.web3signer.dsl.tls.TlsCertificateDefinition; import tech.pegasys.web3signer.signing.config.AwsVaultParameters; import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -81,6 +82,7 @@ public class SignerConfiguration { private final Optional v3KeystoresBulkloadParameters; private final boolean signingExtEnabled; + private final CommitBoostParameters commitBoostParameters; public SignerConfiguration( final String hostname, @@ -128,7 +130,8 @@ public SignerConfiguration( final Optional downstreamTlsOptions, final ChainIdProvider chainIdProvider, final Optional v3KeystoresBulkloadParameters, - final boolean signingExtEnabled) { + final boolean signingExtEnabled, + final CommitBoostParameters commitBoostParameters) { this.hostname = hostname; this.logLevel = logLevel; this.httpRpcPort = httpRpcPort; @@ -175,6 +178,7 @@ public SignerConfiguration( this.chainIdProvider = chainIdProvider; this.v3KeystoresBulkloadParameters = v3KeystoresBulkloadParameters; this.signingExtEnabled = signingExtEnabled; + this.commitBoostParameters = commitBoostParameters; } public String hostname() { @@ -368,4 +372,8 @@ public Optional getV3KeystoresBulkloadParameters() { public boolean isSigningExtEnabled() { return signingExtEnabled; } + + public Optional getCommitBoostParameters() { + return Optional.ofNullable(commitBoostParameters); + } } 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 d396d5303..d9950b37d 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 @@ -22,6 +22,7 @@ import tech.pegasys.web3signer.dsl.tls.TlsCertificateDefinition; import tech.pegasys.web3signer.signing.config.AwsVaultParameters; import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -85,6 +86,7 @@ public class SignerConfigurationBuilder { private KeystoresParameters v3KeystoresBulkloadParameters; private boolean signingExtEnabled; + private CommitBoostParameters commitBoostParameters; public SignerConfigurationBuilder withLogLevel(final Level logLevel) { this.logLevel = logLevel; @@ -331,6 +333,12 @@ public SignerConfigurationBuilder withSigningExtEnabled(final boolean signingExt return this; } + public SignerConfigurationBuilder withCommitBoostParameters( + final CommitBoostParameters commitBoostParameters) { + this.commitBoostParameters = commitBoostParameters; + return this; + } + public SignerConfiguration build() { if (mode == null) { throw new IllegalArgumentException("Mode cannot be null"); @@ -381,6 +389,7 @@ public SignerConfiguration build() { Optional.ofNullable(downstreamTlsOptions), chainIdProvider, Optional.ofNullable(v3KeystoresBulkloadParameters), - signingExtEnabled); + signingExtEnabled, + commitBoostParameters); } } 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 c909bb37b..0d3addbc5 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 @@ -41,6 +41,7 @@ import tech.pegasys.web3signer.dsl.utils.DatabaseUtil; import tech.pegasys.web3signer.signing.config.AwsVaultParameters; import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -163,6 +164,12 @@ public List createCmdLineParams() { String.format(YAML_BOOLEAN_FMT, "eth2.Xsigning-ext-enabled", Boolean.TRUE)); } + signerConfig + .getCommitBoostParameters() + .ifPresent( + commitBoostParameters -> + appendCommitBoostParameters(commitBoostParameters, yamlConfig)); + final CommandArgs subCommandArgs = createSubCommandArgs(); params.addAll(subCommandArgs.params); yamlConfig.append(subCommandArgs.yamlConfig); @@ -204,6 +211,23 @@ public List createCmdLineParams() { return params; } + private static void appendCommitBoostParameters( + final CommitBoostParameters commitBoostParameters, final StringBuilder yamlConfig) { + yamlConfig.append( + String.format( + YAML_BOOLEAN_FMT, "eth2.commit-boost-api-enabled", commitBoostParameters.isEnabled())); + yamlConfig.append( + String.format( + YAML_STRING_FMT, + "eth2.proxy-keystores-path", + commitBoostParameters.getProxyKeystoresPath().toAbsolutePath())); + yamlConfig.append( + String.format( + YAML_STRING_FMT, + "eth2.proxy-keystores-password-file", + commitBoostParameters.getProxyKeystoresPasswordFile().toAbsolutePath())); + } + private Consumer setV3KeystoresBulkloadParameters( final StringBuilder yamlConfig) { return keystoresParameters -> { 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 d5725f30d..6c4fc9da4 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 @@ -41,6 +41,7 @@ import tech.pegasys.web3signer.dsl.utils.DatabaseUtil; import tech.pegasys.web3signer.signing.config.AwsVaultParameters; import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters; import tech.pegasys.web3signer.signing.config.KeystoresParameters; @@ -53,6 +54,7 @@ import java.util.function.Consumer; import com.google.common.collect.Lists; +import org.jetbrains.annotations.NotNull; public class CmdLineParamsDefaultImpl implements CmdLineParamsBuilder { private final SignerConfiguration signerConfig; @@ -138,6 +140,12 @@ public List createCmdLineParams() { if (signerConfig.isSigningExtEnabled()) { params.add("--Xsigning-ext-enabled=true"); } + + signerConfig + .getCommitBoostParameters() + .ifPresent( + commitBoostParameters -> params.addAll(commitBoostOptions(commitBoostParameters))); + } else if (signerConfig.getMode().equals("eth1")) { params.add("--downstream-http-port"); params.add(Integer.toString(signerConfig.getDownstreamHttpPort())); @@ -160,6 +168,16 @@ public List createCmdLineParams() { return params; } + private static @NotNull List commitBoostOptions( + CommitBoostParameters commitBoostParameters) { + return List.of( + "--commit-boost-api-enabled=" + commitBoostParameters.isEnabled(), + "--proxy-keystores-path", + commitBoostParameters.getProxyKeystoresPath().toAbsolutePath().toString(), + "--proxy-keystores-password-file", + commitBoostParameters.getProxyKeystoresPasswordFile().toAbsolutePath().toString()); + } + private static Consumer setV3KeystoresBulkloadParameters( final List params) { return keystoresParameters -> { diff --git a/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/CommitBoostATParameters.java b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/CommitBoostATParameters.java new file mode 100644 index 000000000..ea12f9a05 --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/dsl/utils/CommitBoostATParameters.java @@ -0,0 +1,60 @@ +/* + * 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.dsl.utils; + +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; + +import java.nio.file.Path; +import java.util.Objects; + +public class CommitBoostATParameters implements CommitBoostParameters { + private final boolean enabled; + private final Path proxyKeystoresPath; + private final Path proxyKeystoresPasswordFile; + + public CommitBoostATParameters( + final boolean enabled, final Path proxyKeystoresPath, final Path proxyKeystoresPasswordFile) { + this.enabled = enabled; + this.proxyKeystoresPath = proxyKeystoresPath; + this.proxyKeystoresPasswordFile = proxyKeystoresPasswordFile; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public Path getProxyKeystoresPath() { + return proxyKeystoresPath; + } + + @Override + public Path getProxyKeystoresPasswordFile() { + return proxyKeystoresPasswordFile; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CommitBoostATParameters that)) return false; + return enabled == that.enabled + && Objects.equals(proxyKeystoresPath, that.proxyKeystoresPath) + && Objects.equals(proxyKeystoresPasswordFile, that.proxyKeystoresPasswordFile); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, proxyKeystoresPath, proxyKeystoresPasswordFile); + } +} 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/CommitBoostAcceptanceTest.java new file mode 100644 index 000000000..69b4adb3b --- /dev/null +++ b/acceptance-tests/src/test/java/tech/pegasys/web3signer/tests/commitboost/CommitBoostAcceptanceTest.java @@ -0,0 +1,253 @@ +/* + * 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 org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +import tech.pegasys.teku.bls.BLS; +import tech.pegasys.teku.bls.BLSKeyPair; +import tech.pegasys.teku.bls.BLSPublicKey; +import tech.pegasys.teku.bls.BLSSignature; +import tech.pegasys.teku.networks.Eth2NetworkConfiguration; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.networks.Eth2Network; +import tech.pegasys.web3signer.BLSTestUtil; +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.ProxyDelegation; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; +import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder; +import tech.pegasys.web3signer.dsl.utils.CommitBoostATParameters; +import tech.pegasys.web3signer.dsl.utils.DefaultKeystoresParameters; +import tech.pegasys.web3signer.signing.config.CommitBoostParameters; +import tech.pegasys.web3signer.signing.config.KeystoresParameters; +import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils; +import tech.pegasys.web3signer.tests.AcceptanceTestBase; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hamcrest.TypeSafeMatcher; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +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; +import org.web3j.crypto.Keys; +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 static final SigningRootGenerator SIGNING_ROOT_GENERATOR = + new SigningRootGenerator(getSpec(), Bytes32.ZERO); + private static final BLSKeyPair KEY_PAIR_1 = BLSTestUtil.randomKeyPair(0); + private static final List PROXYY_BLS_KEYS = randomBLSKeyPairs(); + private static final List PROXY_SECP_KEYS = randomECKeyPairs(); + + private static final String KEYSTORE_PASSWORD = "password"; + @TempDir private Path keystoreDir; + @TempDir private Path passwordDir; + // commit boost directories + @TempDir private Path commitBoostKeystoresPath; + @TempDir private Path commitBoostPasswordDir; + + private static List randomECKeyPairs() { + + try { + return List.of(Keys.createEcKeyPair(), Keys.createEcKeyPair()); + } catch (final Exception e) { + throw new IllegalStateException(e); + } + } + + private static List randomBLSKeyPairs() { + return List.of(BLSTestUtil.randomKeyPair(1), BLSTestUtil.randomKeyPair(2)); + } + + @BeforeEach + void setup() throws Exception { + // create main bls key + KeystoreUtil.createKeystore(KEY_PAIR_1, keystoreDir, passwordDir, KEYSTORE_PASSWORD); + + // commit boost proxy keys password file + final Path commitBoostPasswordFile = createCommitBoostPasswordFile(); + + createProxyBLSKeys(); + + createProxyECKeys(); + + // start web3signer with keystores and commit boost parameters + final KeystoresParameters keystoresParameters = + new DefaultKeystoresParameters(keystoreDir, passwordDir, null); + final CommitBoostParameters commitBoostParameters = + new CommitBoostATParameters(true, commitBoostKeystoresPath, commitBoostPasswordFile); + + final SignerConfigurationBuilder configBuilder = + new SignerConfigurationBuilder() + .withMode("eth2") + .withNetwork("mainnet") + .withKeystoresParameters(keystoresParameters) + .withCommitBoostParameters(commitBoostParameters); + + startSigner(configBuilder.build()); + } + + @Test + void listCommitBoostPublicKeys() { + final List proxyBlsPubKeys = getBlsProxyPubKeys(); + final List proxyECPubKeys = getProxyECPubKeys(); + + final Response response = signer.callCommitBoostGetPubKeys(); + // the response should have 1 keys entry. + response + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("keys", hasSize(1)) + .body("keys[0].consensus", equalTo(KEY_PAIR_1.getPublicKey().toHexString())) + .body("keys[0].proxy_bls", containsInAnyOrder(proxyBlsPubKeys.toArray())) + .body("keys[0].proxy_ecdsa", containsInAnyOrder(proxyECPubKeys.toArray())); + } + + @ParameterizedTest + @EnumSource(ProxyKeySignatureScheme.class) + void generateCommitBoostProxyKeys(final ProxyKeySignatureScheme scheme) { + final Response response = + signer.callCommitBoostGenerateProxyKey( + KEY_PAIR_1.getPublicKey().toHexString(), scheme.name()); + // verify we got new proxy public key and a valid bls signature + response + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body("message.delegator", equalTo(KEY_PAIR_1.getPublicKey().toHexString())) + .body( + "signature", + resp -> { + final String messageProxy = resp.path("message.proxy"); + final String delegator = resp.path("message.delegator"); + final Bytes32 hashTreeRoot = + new ProxyDelegation(delegator, messageProxy) + .toMerkleizable(scheme) + .hashTreeRoot(); + final Bytes32 signingRoot = SIGNING_ROOT_GENERATOR.computeSigningRoot(hashTreeRoot); + return new ValidBLSSignatureMatcher(KEY_PAIR_1.getPublicKey(), signingRoot); + }); + + // the number of (scheme) proxy public keys should've increased + final Response pubKeyResponse = signer.callCommitBoostGetPubKeys(); + final List updatedProxyKeys = + new ArrayList<>( + scheme == ProxyKeySignatureScheme.BLS ? getBlsProxyPubKeys() : getProxyECPubKeys()); + updatedProxyKeys.add(response.path("message.proxy")); + assertThat(updatedProxyKeys).hasSize(3); + + final String jsonMatch = "keys[0].proxy_" + scheme.name().toLowerCase(); + pubKeyResponse + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body(jsonMatch, containsInAnyOrder(updatedProxyKeys.toArray())); + } + + private static Spec getSpec() { + final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); + builder.applyNetworkDefaults(Eth2Network.MAINNET); + Eth2NetworkConfiguration eth2NetworkConfiguration = builder.build(); + return eth2NetworkConfiguration.getSpec(); + } + + private static List getProxyECPubKeys() { + return PROXY_SECP_KEYS.stream() + .map( + ecKeyPair -> + EthPublicKeyUtils.getEncoded( + EthPublicKeyUtils.bigIntegerToECPublicKey(ecKeyPair.getPublicKey()), true) + .toHexString()) + .toList(); + } + + private static List getBlsProxyPubKeys() { + return PROXYY_BLS_KEYS.stream() + .map(blsKeyPair -> blsKeyPair.getPublicKey().toHexString()) + .toList(); + } + + private Path createCommitBoostPasswordFile() { + try { + return Files.writeString( + commitBoostPasswordDir.resolve("cb_password.txt"), KEYSTORE_PASSWORD); + } catch (IOException e) { + throw new IllegalStateException("Unable to write password file"); + } + } + + private void createProxyECKeys() throws IOException { + final Path proxySecpKeyStoreDir = + commitBoostKeystoresPath + .resolve(KEY_PAIR_1.getPublicKey().toHexString()) + .resolve("SECP256K1"); + Files.createDirectories(proxySecpKeyStoreDir); + PROXY_SECP_KEYS.forEach( + proxyECKey -> { + try { + WalletUtils.generateWalletFile( + KEYSTORE_PASSWORD, proxyECKey, proxySecpKeyStoreDir.toFile(), false); + } catch (Exception e) { + throw new IllegalStateException(e); + } + }); + } + + private void createProxyBLSKeys() throws IOException { + final Path proxyBlsKeyStoreDir = + commitBoostKeystoresPath.resolve(KEY_PAIR_1.getPublicKey().toHexString()).resolve("BLS"); + Files.createDirectories(proxyBlsKeyStoreDir); + PROXYY_BLS_KEYS.forEach( + proxyBlsKey -> + KeystoreUtil.createKeystoreFile(proxyBlsKey, proxyBlsKeyStoreDir, KEYSTORE_PASSWORD)); + } + + static class ValidBLSSignatureMatcher extends TypeSafeMatcher { + private final BLSPublicKey blsPublicKey; + private final Bytes32 signingRoot; + + public ValidBLSSignatureMatcher(final BLSPublicKey blsPublicKey, final Bytes32 signingRoot) { + this.blsPublicKey = blsPublicKey; + this.signingRoot = signingRoot; + } + + @Override + protected boolean matchesSafely(final String signature) { + final BLSSignature blsSignature = + BLSSignature.fromBytesCompressed(Bytes.fromHexString(signature)); + return BLS.verify(blsPublicKey, signingRoot, blsSignature); + } + + @Override + public void describeTo(final org.hamcrest.Description description) { + description.appendText("a valid BLS signature"); + } + } +} From a1ed50c96c7e7fbdae923bc3b1fe8607804e3814 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 14 Nov 2024 17:28:46 +0700 Subject: [PATCH 56/57] Commit Boost Acceptance Test -- Fix incorrect logic of lookig up proxy signers --- .../pegasys/web3signer/dsl/signer/Signer.java | 18 +++++ .../CommitBoostAcceptanceTest.java | 67 +++++++++++++++++-- .../CommitBoostGenerateProxyKeyHandler.java | 14 ++-- .../CommitBoostPublicKeysHandler.java | 9 +-- .../CommitBoostSignerProvider.java | 26 +++---- .../signing/ArtifactSignerProvider.java | 10 +-- .../config/DefaultArtifactSignerProvider.java | 12 ++-- .../signing/secp256k1/EthPublicKeyUtils.java | 17 +++-- .../signing/K256ArtifactSignerTest.java | 41 +++--------- .../tech/pegasys/web3signer/K256TestUtil.java | 52 ++++++++++++++ 10 files changed, 185 insertions(+), 81 deletions(-) create mode 100644 signing/src/testFixtures/java/tech/pegasys/web3signer/K256TestUtil.java 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 f6776558c..a2fe80906 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,6 +19,7 @@ import static tech.pegasys.web3signer.signing.KeyType.BLS; import tech.pegasys.web3signer.core.service.http.SigningObjectMapperFactory; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; import tech.pegasys.web3signer.core.service.http.handlers.signing.eth2.Eth2SigningRequestBody; import tech.pegasys.web3signer.dsl.Accounts; import tech.pegasys.web3signer.dsl.Eth; @@ -47,6 +48,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.Ethereum; import org.web3j.protocol.core.JsonRpc2_0Web3j; @@ -199,6 +201,22 @@ public Response callCommitBoostGenerateProxyKey(final String pubkey, final Strin .post("/signer/v1/generate_proxy_key"); } + public Response callCommitBoostReqeustForSignature( + final SignRequestType type, final String pubkey, final Bytes32 objectRoot) { + return given() + .baseUri(getUrl()) + .contentType(ContentType.JSON) + .log() + .all() + .body( + new JsonObject() + .put("type", type.name().toLowerCase()) + .put("pubkey", pubkey) + .put("object_root", objectRoot.toHexString()) + .toString()) + .post("/signer/v1/request_signature"); + } + public List listPublicKeys(final KeyType keyType) { return callApiPublicKeys(keyType).as(new TypeRef<>() {}); } 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/CommitBoostAcceptanceTest.java index 69b4adb3b..7e6b12911 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/CommitBoostAcceptanceTest.java @@ -25,10 +25,12 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.web3signer.BLSTestUtil; +import tech.pegasys.web3signer.K256TestUtil; 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.ProxyDelegation; import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.ProxyKeySignatureScheme; +import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder; import tech.pegasys.web3signer.dsl.utils.CommitBoostATParameters; import tech.pegasys.web3signer.dsl.utils.DefaultKeystoresParameters; @@ -42,11 +44,13 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Random; import io.restassured.http.ContentType; import io.restassured.response.Response; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.bouncycastle.math.ec.ECPoint; import org.hamcrest.TypeSafeMatcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -152,7 +156,7 @@ void generateCommitBoostProxyKeys(final ProxyKeySignatureScheme scheme) { .toMerkleizable(scheme) .hashTreeRoot(); final Bytes32 signingRoot = SIGNING_ROOT_GENERATOR.computeSigningRoot(hashTreeRoot); - return new ValidBLSSignatureMatcher(KEY_PAIR_1.getPublicKey(), signingRoot); + return new ValidBLSSignatureMatcher(delegator, signingRoot); }); // the number of (scheme) proxy public keys should've increased @@ -171,6 +175,40 @@ void generateCommitBoostProxyKeys(final ProxyKeySignatureScheme scheme) { .body(jsonMatch, containsInAnyOrder(updatedProxyKeys.toArray())); } + @ParameterizedTest + @EnumSource(SignRequestType.class) + void requestCommitBoostSignature(final SignRequestType signRequestType) { + final String pubKey = + switch (signRequestType) { + case CONSENSUS -> KEY_PAIR_1.getPublicKey().toHexString(); + case PROXY_BLS -> + PROXYY_BLS_KEYS.stream().findFirst().orElseThrow().getPublicKey().toHexString(); + case PROXY_ECDSA -> + EthPublicKeyUtils.getEncoded( + EthPublicKeyUtils.bigIntegerToECPublicKey( + PROXY_SECP_KEYS.stream().findFirst().orElseThrow().getPublicKey()), + true) + .toHexString(); + }; + + // 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.callCommitBoostReqeustForSignature(signRequestType, pubKey, objectRoot); + + response + .then() + .statusCode(200) + .contentType(ContentType.JSON) + .body( + signRequestType == SignRequestType.PROXY_ECDSA + ? new ValidK256K1SignatureMatcher(pubKey, signingRoot) + : new ValidBLSSignatureMatcher(pubKey, signingRoot)); + } + private static Spec getSpec() { final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder(); builder.applyNetworkDefaults(Eth2Network.MAINNET); @@ -233,15 +271,15 @@ static class ValidBLSSignatureMatcher extends TypeSafeMatcher { private final BLSPublicKey blsPublicKey; private final Bytes32 signingRoot; - public ValidBLSSignatureMatcher(final BLSPublicKey blsPublicKey, final Bytes32 signingRoot) { - this.blsPublicKey = blsPublicKey; + public ValidBLSSignatureMatcher(final String blsPublicKey, final Bytes32 signingRoot) { + this.blsPublicKey = BLSPublicKey.fromHexString(blsPublicKey); this.signingRoot = signingRoot; } @Override protected boolean matchesSafely(final String signature) { final BLSSignature blsSignature = - BLSSignature.fromBytesCompressed(Bytes.fromHexString(signature)); + BLSSignature.fromBytesCompressed(Bytes.fromHexString(signature.replace("\"", ""))); return BLS.verify(blsPublicKey, signingRoot, blsSignature); } @@ -250,4 +288,25 @@ public void describeTo(final org.hamcrest.Description description) { description.appendText("a valid BLS signature"); } } + + static class ValidK256K1SignatureMatcher extends TypeSafeMatcher { + private final ECPoint ecPoint; + private final Bytes32 signingRoot; + + public ValidK256K1SignatureMatcher(final String ecPubKeyHex, final Bytes32 signingRoot) { + this.ecPoint = EthPublicKeyUtils.bytesToBCECPoint(Bytes.fromHexString(ecPubKeyHex)); + this.signingRoot = signingRoot; + } + + @Override + protected boolean matchesSafely(final String signature) { + final byte[] sig = Bytes.fromHexString(signature.replace("\"", "")).toArray(); + return K256TestUtil.verifySignature(ecPoint, signingRoot.toArray(), sig); + } + + @Override + public void describeTo(final org.hamcrest.Description description) { + description.appendText("a valid K256 signature"); + } + } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java index c6d81952d..8d88bd62d 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostGenerateProxyKeyHandler.java @@ -68,9 +68,9 @@ public void handle(final RoutingContext context) { } // Check for identifier, if not exist, fail with 404 - final String identifier = normaliseIdentifier(proxyKeyBody.blsPublicKey()); + final String consensusPubKey = normaliseIdentifier(proxyKeyBody.blsPublicKey()); final boolean signerAvailable = - commitBoostSignerProvider.isSignerAvailable(identifier, SignRequestType.CONSENSUS); + commitBoostSignerProvider.isSignerAvailable(consensusPubKey, SignRequestType.CONSENSUS); if (!signerAvailable) { context.fail(NOT_FOUND); return; @@ -80,20 +80,20 @@ public void handle(final RoutingContext context) { // Generate actual proxy key and encrypted keystore based on signature scheme final ArtifactSigner proxyArtifactSigner = switch (proxyKeyBody.scheme()) { - case BLS -> proxyKeyGenerator.generateBLSProxyKey(identifier); - case ECDSA -> proxyKeyGenerator.generateECProxyKey(identifier); + case BLS -> proxyKeyGenerator.generateBLSProxyKey(consensusPubKey); + case ECDSA -> proxyKeyGenerator.generateECProxyKey(consensusPubKey); }; // Add generated proxy ArtifactSigner to ArtifactSignerProvider - commitBoostSignerProvider.addProxySigner(proxyArtifactSigner, identifier); + commitBoostSignerProvider.addProxySigner(proxyArtifactSigner, consensusPubKey); final ProxyDelegation proxyDelegation = - new ProxyDelegation(identifier, proxyArtifactSigner.getIdentifier()); + new ProxyDelegation(consensusPubKey, proxyArtifactSigner.getIdentifier()); final Bytes32 signingRoot = signingRootGenerator.computeSigningRoot( proxyDelegation.toMerkleizable(proxyKeyBody.scheme()).hashTreeRoot()); final Optional optionalSig = - commitBoostSignerProvider.sign(identifier, SignRequestType.CONSENSUS, signingRoot); + commitBoostSignerProvider.sign(consensusPubKey, SignRequestType.CONSENSUS, signingRoot); if (optionalSig.isEmpty()) { context.fail(NOT_FOUND); return; diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java index 95820e4d8..318af30f0 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostPublicKeysHandler.java @@ -57,17 +57,18 @@ public void handle(final RoutingContext context) { private PublicKeysResponse toPublicKeysResponse(final ArtifactSignerProvider provider) { return new PublicKeysResponse( provider.availableIdentifiers().stream() - .map(identifier -> toPublicKeyMappings(provider, identifier)) + .map(consensusPubKey -> toPublicKeyMappings(provider, consensusPubKey)) .collect(Collectors.toList())); } private static PublicKeyMappings toPublicKeyMappings( - final ArtifactSignerProvider provider, final String identifier) { - final Map> proxyIdentifiers = provider.getProxyIdentifiers(identifier); + final ArtifactSignerProvider provider, final String consensusPubKey) { + final Map> proxyIdentifiers = + provider.getProxyIdentifiers(consensusPubKey); final Set proxyBlsPublicKeys = proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> Set.of()); final Set proxyEcdsaPublicKeys = proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> Set.of()); - return new PublicKeyMappings(identifier, proxyBlsPublicKeys, proxyEcdsaPublicKeys); + return new PublicKeyMappings(consensusPubKey, proxyBlsPublicKeys, proxyEcdsaPublicKeys); } } diff --git a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java index c2276f9cd..9fbc7d1eb 100644 --- a/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java +++ b/core/src/main/java/tech/pegasys/web3signer/core/service/http/handlers/commitboost/CommitBoostSignerProvider.java @@ -15,11 +15,8 @@ import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.SignRequestType; import tech.pegasys.web3signer.signing.ArtifactSigner; import tech.pegasys.web3signer.signing.ArtifactSignerProvider; -import tech.pegasys.web3signer.signing.KeyType; -import java.util.Map; import java.util.Optional; -import java.util.Set; import org.apache.tuweni.bytes.Bytes32; @@ -49,18 +46,7 @@ public CommitBoostSignerProvider(final ArtifactSignerProvider artifactSignerProv public boolean isSignerAvailable(final String identifier, final SignRequestType type) { return switch (type) { case CONSENSUS -> artifactSignerProvider.availableIdentifiers().contains(identifier); - case PROXY_BLS -> { - final Map> proxyIdentifiers = - artifactSignerProvider.getProxyIdentifiers(identifier); - yield proxyIdentifiers.containsKey(KeyType.BLS) - && proxyIdentifiers.get(KeyType.BLS).contains(identifier); - } - case PROXY_ECDSA -> { - final Map> proxyIdentifiers = - artifactSignerProvider.getProxyIdentifiers(identifier); - yield proxyIdentifiers.containsKey(KeyType.SECP256K1) - && proxyIdentifiers.get(KeyType.SECP256K1).contains(identifier); - } + case PROXY_BLS, PROXY_ECDSA -> artifactSignerProvider.getProxySigner(identifier).isPresent(); }; } @@ -83,7 +69,13 @@ public Optional sign( return optionalArtifactSigner.map(signer -> signer.sign(signingRoot).asHex()); } - public void addProxySigner(final ArtifactSigner artifactSigner, final String identifier) { - artifactSignerProvider.addProxySigner(artifactSigner, identifier); + /** + * Add a proxy signer to the provider + * + * @param artifactSigner The proxy signer to add + * @param consensusPubKey The consensus public key to associate with the proxy signer + */ + public void addProxySigner(final ArtifactSigner artifactSigner, final String consensusPubKey) { + artifactSignerProvider.addProxySigner(artifactSigner, consensusPubKey); } } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java index 21ea2972b..ffc41c5eb 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java @@ -53,12 +53,12 @@ default Optional getProxySigner(final String identifier) { Set availableIdentifiers(); /** - * Get the proxy identifiers for the given identifier. Used for commit boost API. + * Get the proxy public keys for the given consensus public key. Used for commit boost API. * - * @param identifier the identifier of the signer + * @param consensusPubKey the identifier of the consensus signer * @return Map of Key Type (BLS, SECP256K1) and corresponding proxy identifiers */ - default Map> getProxyIdentifiers(final String identifier) { + default Map> getProxyIdentifiers(final String consensusPubKey) { throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); } @@ -82,10 +82,10 @@ default Map> getProxyIdentifiers(final String identifier) { * Add a proxy signer to the signer provider. * * @param signer Instance of ArtifactSigner - * @param identifier Identifier of the signer for which proxy signer is being added + * @param consensusPubKey Public Key of the consensus signer for which proxy signer is being added * @return a future that completes when the proxy signer is added */ - default Future addProxySigner(final ArtifactSigner signer, final String identifier) { + default Future addProxySigner(final ArtifactSigner signer, final String consensusPubKey) { throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index e3afa9b21..00565f968 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -128,9 +128,9 @@ public Set availableIdentifiers() { } @Override - public Map> getProxyIdentifiers(final String identifier) { + public Map> getProxyIdentifiers(final String consensusPubKey) { final Set artifactSigners = - proxySigners.computeIfAbsent(identifier, k -> Set.of()); + proxySigners.computeIfAbsent(consensusPubKey, k -> Set.of()); return artifactSigners.stream() .collect( Collectors.groupingBy( @@ -160,12 +160,14 @@ public Future removeSigner(final String identifier) { } @Override - public Future addProxySigner(final ArtifactSigner signer, final String identifier) { + public Future addProxySigner(final ArtifactSigner signer, final String consensusPubKey) { return executorService.submit( () -> { - proxySigners.computeIfAbsent(identifier, k -> new HashSet<>()).add(signer); + proxySigners.computeIfAbsent(consensusPubKey, k -> new HashSet<>()).add(signer); LOG.info( - "Loaded new proxy signer {} for identifier '{}'", signer.getIdentifier(), identifier); + "Loaded new proxy signer {} for consensus public key '{}'", + signer.getIdentifier(), + consensusPubKey); return null; }); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java index 074987554..ea63f3d38 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/secp256k1/EthPublicKeyUtils.java @@ -106,23 +106,28 @@ public static KeyPair web3JECKeypairToJavaKeyPair(final ECKeyPair web3JECKeypair * @return The ECPublicKey */ public static ECPublicKey bytesToECPublicKey(final Bytes value) { + return bcECPointToECPublicKey(bytesToBCECPoint(value)); + } + + public static ECPoint bytesToBCECPoint(final Bytes value) { if (value.size() != 33 && value.size() != 65 && value.size() != 64) { throw new IllegalArgumentException( "Invalid public key length. Expected 33, 64, or 65 bytes."); } final ECPoint point; + final byte[] key; if (value.size() == 64) { // For 64-byte input, we need to prepend the 0x04 prefix for uncompressed format - byte[] fullKey = new byte[65]; - fullKey[0] = 0x04; - System.arraycopy(value.toArrayUnsafe(), 0, fullKey, 1, 64); - point = SECP256K1_DOMAIN.getCurve().decodePoint(fullKey); + key = new byte[65]; + key[0] = 0x04; + System.arraycopy(value.toArrayUnsafe(), 0, key, 1, 64); } else { - point = SECP256K1_DOMAIN.getCurve().decodePoint(value.toArrayUnsafe()); + key = value.toArrayUnsafe(); } + point = SECP256K1_DOMAIN.getCurve().decodePoint(key); - return bcECPointToECPublicKey(point); + return point; } private static ECPublicKey bcECPointToECPublicKey(final ECPoint point) { diff --git a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java index dc3356e4a..6abdf9898 100644 --- a/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java +++ b/signing/src/test/java/tech/pegasys/web3signer/signing/K256ArtifactSignerTest.java @@ -13,18 +13,10 @@ package tech.pegasys.web3signer.signing; import static org.assertj.core.api.Assertions.assertThat; -import static tech.pegasys.web3signer.signing.K256ArtifactSigner.CURVE; -import static tech.pegasys.web3signer.signing.K256ArtifactSigner.calculateSHA256; -import java.math.BigInteger; -import java.util.Arrays; +import tech.pegasys.web3signer.K256TestUtil; import org.apache.tuweni.bytes.Bytes; -import org.bouncycastle.crypto.digests.SHA256Digest; -import org.bouncycastle.crypto.params.ECPublicKeyParameters; -import org.bouncycastle.crypto.signers.ECDSASigner; -import org.bouncycastle.crypto.signers.HMacDSAKCalculator; -import org.bouncycastle.math.ec.ECPoint; import org.junit.jupiter.api.Test; import org.web3j.crypto.ECKeyPair; import org.web3j.crypto.Sign; @@ -42,9 +34,15 @@ void signCreatesVerifiableSignature() { // generate using K256ArtifactSigner final K256ArtifactSigner k256ArtifactSigner = new K256ArtifactSigner(EC_KEY_PAIR); final ArtifactSignature artifactSignature = k256ArtifactSigner.sign(OBJECT_ROOT); + final byte[] signature = Bytes.fromHexString(artifactSignature.asHex()).toArray(); // Verify the signature against public key - assertThat(verifySignature(artifactSignature)).isTrue(); + assertThat( + K256TestUtil.verifySignature( + Sign.publicPointFromPrivate(EC_KEY_PAIR.getPrivateKey()), + OBJECT_ROOT.toArray(), + signature)) + .isTrue(); // copied from Rust K-256 and Python ecdsa module final Bytes expectedSignature = @@ -52,27 +50,4 @@ void signCreatesVerifiableSignature() { "8C32902BE980399CA59FCC222CCF0A5FE355A159122DEA58789A3938E29D89797FC6C9C0ECCCD29705915729F5326BB7D245F8E54D3A793A06DE3C92ABA85057"); assertThat(Bytes.fromHexString(artifactSignature.asHex())).isEqualTo(expectedSignature); } - - boolean verifySignature(final ArtifactSignature signature) { - try { - // we are assuming that we got 64 bytes signature in R+S format - byte[] concatenated = Bytes.fromHexString(signature.asHex()).toArray(); - byte[] rBytes = Arrays.copyOfRange(concatenated, 0, 32); - byte[] sBytes = Arrays.copyOfRange(concatenated, 32, 64); - - final BigInteger r = new BigInteger(1, rBytes); - final BigInteger s = new BigInteger(1, sBytes); - - final ECPoint pubECPoint = Sign.publicPointFromPrivate(EC_KEY_PAIR.getPrivateKey()); - final ECPublicKeyParameters ecPublicKeyParameters = - new ECPublicKeyParameters(pubECPoint, CURVE); - - final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); - signer.init(false, ecPublicKeyParameters); - // apply sha-256 before verification - return signer.verifySignature(calculateSHA256(OBJECT_ROOT.toArray()), r, s); - } catch (Exception e) { - throw new RuntimeException("Error verifying signature", e); - } - } } diff --git a/signing/src/testFixtures/java/tech/pegasys/web3signer/K256TestUtil.java b/signing/src/testFixtures/java/tech/pegasys/web3signer/K256TestUtil.java new file mode 100644 index 000000000..86139a24e --- /dev/null +++ b/signing/src/testFixtures/java/tech/pegasys/web3signer/K256TestUtil.java @@ -0,0 +1,52 @@ +/* + * 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; + +import static tech.pegasys.web3signer.signing.K256ArtifactSigner.CURVE; +import static tech.pegasys.web3signer.signing.K256ArtifactSigner.calculateSHA256; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.params.ECPublicKeyParameters; +import org.bouncycastle.crypto.signers.ECDSASigner; +import org.bouncycastle.crypto.signers.HMacDSAKCalculator; +import org.bouncycastle.math.ec.ECPoint; + +public class K256TestUtil { + public static boolean verifySignature( + final ECPoint pubECPoint, final byte[] message, final byte[] compactSignature) { + try { + if (compactSignature.length != 64) { + throw new IllegalStateException("Expecting 64 bytes signature in R+S format"); + } + // we are assuming that we got 64 bytes signature in R+S format + byte[] rBytes = Arrays.copyOfRange(compactSignature, 0, 32); + byte[] sBytes = Arrays.copyOfRange(compactSignature, 32, 64); + + final BigInteger r = new BigInteger(1, rBytes); + final BigInteger s = new BigInteger(1, sBytes); + + final ECPublicKeyParameters ecPublicKeyParameters = + new ECPublicKeyParameters(pubECPoint, CURVE); + + final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest())); + signer.init(false, ecPublicKeyParameters); + // apply sha-256 before verification + return signer.verifySignature(calculateSHA256(message), r, s); + } catch (Exception e) { + throw new RuntimeException("Error verifying signature", e); + } + } +} From 75c3e502bddeb0ac8dba3cd7ee092dd112ab9f20 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Thu, 14 Nov 2024 17:50:33 +0700 Subject: [PATCH 57/57] minor cleanup --- .../dsl/signer/runner/CmdLineParamsDefaultImpl.java | 4 +--- .../pegasys/web3signer/signing/ArtifactSignerProvider.java | 6 +++--- .../pegasys/web3signer/signing/SecpArtifactSignature.java | 7 ------- .../signing/config/DefaultArtifactSignerProvider.java | 4 ++-- 4 files changed, 6 insertions(+), 15 deletions(-) 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 6c4fc9da4..ffd1d9def 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 @@ -54,7 +54,6 @@ import java.util.function.Consumer; import com.google.common.collect.Lists; -import org.jetbrains.annotations.NotNull; public class CmdLineParamsDefaultImpl implements CmdLineParamsBuilder { private final SignerConfiguration signerConfig; @@ -168,8 +167,7 @@ public List createCmdLineParams() { return params; } - private static @NotNull List commitBoostOptions( - CommitBoostParameters commitBoostParameters) { + private static List commitBoostOptions(CommitBoostParameters commitBoostParameters) { return List.of( "--commit-boost-api-enabled=" + commitBoostParameters.isEnabled(), "--proxy-keystores-path", diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java index ffc41c5eb..87d1698c5 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/ArtifactSignerProvider.java @@ -36,12 +36,12 @@ public interface ArtifactSignerProvider extends Closeable { Optional getSigner(final String identifier); /** - * Get the proxy signer for the given identifier. + * Get the proxy signer for the given proxy public key. * - * @param identifier the identifier of the signer + * @param proxyPubKey the public key of the proxy signer * @return the signer or empty if no signer is found */ - default Optional getProxySigner(final String identifier) { + default Optional getProxySigner(final String proxyPubKey) { throw new UnsupportedOperationException("Proxy signers are not supported by this provider"); } diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java index be7ad9717..667291917 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/SecpArtifactSignature.java @@ -44,13 +44,6 @@ public String asHex() { .toHexString(); } - public String asEncodedHex() { - return Bytes.concatenate( - Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getR()))), - Bytes32.leftPad(Bytes.wrap(ByteUtils.bigIntegerToBytes(signature.getS())))) - .toHexString(); - } - public static SecpArtifactSignature fromBytes(final Bytes signature) { final Bytes r = signature.slice(0, 32); final Bytes s = signature.slice(32, 32); diff --git a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java index 00565f968..c87bf52b3 100644 --- a/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java +++ b/signing/src/main/java/tech/pegasys/web3signer/signing/config/DefaultArtifactSignerProvider.java @@ -115,10 +115,10 @@ public Optional getSigner(final String identifier) { } @Override - public Optional getProxySigner(final String identifier) { + public Optional getProxySigner(final String proxyPubKey) { return proxySigners.values().stream() .flatMap(Set::stream) - .filter(signer -> signer.getIdentifier().equals(identifier)) + .filter(signer -> signer.getIdentifier().equals(proxyPubKey)) .findFirst(); }