Skip to content

Commit

Permalink
Commit Boost - Request Signature
Browse files Browse the repository at this point in the history
  • Loading branch information
usmansaleem committed Nov 1, 2024
1 parent ebccf38 commit 5a8a343
Show file tree
Hide file tree
Showing 14 changed files with 352 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -71,11 +72,11 @@ private PublicKeysResponse toPublicKeysResponse(final ArtifactSignerProvider pro

private static PublicKeyMappings toPublicKeyMappings(
final ArtifactSignerProvider provider, final String identifier) {
final Map<KeyType, List<String>> proxyIdentifiers = provider.getProxyIdentifiers(identifier);
final List<String> proxyBlsPublicKeys =
proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> List.of());
final List<String> proxyEcdsaPublicKeys =
proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> List.of());
final Map<KeyType, Set<String>> proxyIdentifiers = provider.getProxyIdentifiers(identifier);
final Set<String> proxyBlsPublicKeys =
proxyIdentifiers.computeIfAbsent(KeyType.BLS, k -> Set.of());
final Set<String> proxyEcdsaPublicKeys =
proxyIdentifiers.computeIfAbsent(KeyType.SECP256K1, k -> Set.of());
return new PublicKeyMappings(identifier, proxyBlsPublicKeys, proxyEcdsaPublicKeys);
}
}
Original file line number Diff line number Diff line change
@@ -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<RoutingContext> {
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<String> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<KeyType, Set<String>> proxyIdentifiers =
artifactSignerProvider.getProxyIdentifiers(identifier);
yield proxyIdentifiers.containsKey(KeyType.BLS)
&& proxyIdentifiers.get(KeyType.BLS).contains(identifier);
}
case PROXY_ECDSA -> {
final Map<KeyType, Set<String>> proxyIdentifiers =
artifactSignerProvider.getProxyIdentifiers(identifier);
yield proxyIdentifiers.containsKey(KeyType.SECP256K1)
&& proxyIdentifiers.get(KeyType.SECP256K1).contains(identifier);
}
};
}

public Optional<String> sign(
final String identifier, final SignRequestType type, final Bytes32 signingRoot) {
final Optional<ArtifactSigner> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@
*/
package tech.pegasys.web3signer.core.service.http.handlers.commitboost.json;

import java.util.List;
import java.util.Set;

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
* @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<String> proxyBlsPublicKeys,
@JsonProperty(value = "proxy_ecdsa") List<String> proxyEcdsaPublicKeys) {}
@JsonProperty(value = "proxy_bls") Set<String> proxyBlsPublicKeys,
@JsonProperty(value = "proxy_ecdsa") Set<String> proxyEcdsaPublicKeys) {}
Original file line number Diff line number Diff line change
@@ -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) {}
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit 5a8a343

Please sign in to comment.