Skip to content

Commit

Permalink
feat: Commit boost API - Generate Proxy Key (Consensys#1043)
Browse files Browse the repository at this point in the history
Commit boost API - Generate Proxy Key Implementation 

- Routes and Handler for Generate Proxy Key CommitBoostGenerateProxyKeyHandler, CommitBoostGenerateProxyKeyRoute
- Custom SSZ classes for calculating signing roots (BLSProxyDelegation, SECPProxyDelegation,ProxyDelegation)
- Create BLS and/or SECP proxy keys (utility classes ProxyKeysGenerator and SigningRootGenerator)
- Unit/Acceptance tests
  • Loading branch information
usmansaleem authored Nov 27, 2024
1 parent d5bb7c0 commit c389a3f
Show file tree
Hide file tree
Showing 34 changed files with 1,483 additions and 53 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
### 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)
- Teku and Besu libraries updated to 24.10.3 and 24.10.0 respectively.
- Commit Boost API - Get Public Keys [#1031][cb_pr1], Generate Proxy Keys [#1043][cb_pr2].

[cb_pr1]: https://github.com/Consensys/web3signer/pull/1031
[cb_pr2]: https://github.com/Consensys/web3signer/pull/1043

### Bugs fixed
- Override protobuf-java to 3.25.5 which is a transitive dependency from google-cloud-secretmanager. It fixes CVE-2024-7254.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.teku.bls.BLS;
import tech.pegasys.teku.bls.BLSPublicKey;
import tech.pegasys.teku.bls.BLSSignature;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.hamcrest.TypeSafeMatcher;

/** A Hamcrest matcher that verifies a BLS scheme signature is valid. */
public class ValidBLSSignatureMatcher extends TypeSafeMatcher<String> {
private final BLSPublicKey blsPublicKey;
private final Bytes32 signingRoot;

public ValidBLSSignatureMatcher(final String blsPubHex, final Bytes32 signingRoot) {
this.blsPublicKey = BLSPublicKey.fromHexString(blsPubHex);
this.signingRoot = signingRoot;
}

@Override
protected boolean matchesSafely(final String signature) {
final BLSSignature blsSignature =
BLSSignature.fromBytesCompressed(Bytes.fromHexString(signature.replace("\"", "")));
return BLS.verify(blsPublicKey, signingRoot, blsSignature);
}

@Override
public void describeTo(final org.hamcrest.Description description) {
description.appendText("a valid BLS signature");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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.K256TestUtil;
import tech.pegasys.web3signer.signing.secp256k1.EthPublicKeyUtils;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.bouncycastle.math.ec.ECPoint;
import org.hamcrest.TypeSafeMatcher;

/** A Hamcrest matcher that verifies a K256 scheme signature (R+S format) is valid. */
public class ValidK256SignatureMatcher extends TypeSafeMatcher<String> {

private final ECPoint ecPoint;
private final Bytes32 signingRoot;

public ValidK256SignatureMatcher(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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
import tech.pegasys.web3signer.dsl.signer.SignerConfiguration;
import tech.pegasys.web3signer.signing.KeyType;

import java.security.SecureRandom;
import java.util.Set;

import org.junit.jupiter.api.AfterEach;

public class AcceptanceTestBase {

protected static final SecureRandom SECURE_RANDOM = new SecureRandom();
protected Signer signer;

public static final Long DEFAULT_CHAIN_ID = 1337L;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -45,8 +44,7 @@

// See https://commit-boost.github.io/commit-boost-client/api/ for Commit Boost spec
public class CommitBoostAcceptanceTest extends AcceptanceTestBase {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final String KEYSTORE_PASSWORD = "password";
static final String KEYSTORE_PASSWORD = "password";

private List<BLSKeyPair> consensusBlsKeys = randomBLSKeyPairs(2);
private Map<String, List<BLSKeyPair>> proxyBLSKeysMap = new HashMap<>();
Expand All @@ -73,7 +71,7 @@ void setup() throws Exception {
}

// commit boost proxy keys password file
final Path commitBoostPasswordFile = createCommitBoostPasswordFile();
final Path commitBoostPasswordFile = createCommitBoostPasswordFile(commitBoostPasswordDir);

// start web3signer with keystores and commit boost parameters
final KeystoresParameters keystoresParameters =
Expand Down Expand Up @@ -139,7 +137,7 @@ private List<String> getProxyBLSPubKeys(final String consensusKeyHex) {
.toList();
}

private Path createCommitBoostPasswordFile() {
static Path createCommitBoostPasswordFile(final Path commitBoostPasswordDir) {
try {
return Files.writeString(
commitBoostPasswordDir.resolve("cb_password.txt"), KEYSTORE_PASSWORD);
Expand Down Expand Up @@ -208,7 +206,7 @@ private List<BLSKeyPair> createProxyBLSKeys(final BLSKeyPair consensusKeyPair) {
* @param count number of key pairs to generate
* @return list of ECKeyPairs
*/
private static List<ECKeyPair> randomECKeyPairs(final int count) {
static List<ECKeyPair> randomECKeyPairs(final int count) {
return Stream.generate(
() -> {
try {
Expand All @@ -227,7 +225,7 @@ private static List<ECKeyPair> randomECKeyPairs(final int count) {
* @param count number of key pairs to generate
* @return list of BLSKeyPairs
*/
private static List<BLSKeyPair> randomBLSKeyPairs(final int count) {
static List<BLSKeyPair> randomBLSKeyPairs(final int count) {
return Stream.generate(() -> BLSKeyPair.random(SECURE_RANDOM)).limit(count).toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.KEYSTORE_PASSWORD;
import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.createCommitBoostPasswordFile;
import static tech.pegasys.web3signer.tests.commitboost.CommitBoostAcceptanceTest.randomBLSKeyPairs;

import tech.pegasys.teku.bls.BLSKeyPair;
import tech.pegasys.teku.networks.Eth2NetworkConfiguration;
import tech.pegasys.teku.spec.Spec;
import tech.pegasys.teku.spec.networks.Eth2Network;
import tech.pegasys.web3signer.KeystoreUtil;
import tech.pegasys.web3signer.core.service.http.handlers.commitboost.SigningRootGenerator;
import tech.pegasys.web3signer.core.service.http.handlers.commitboost.json.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.DefaultKeystoresParameters;
import tech.pegasys.web3signer.dsl.utils.ValidBLSSignatureMatcher;
import tech.pegasys.web3signer.signing.config.KeystoresParameters;
import tech.pegasys.web3signer.tests.AcceptanceTestBase;

import java.nio.file.Path;
import java.util.List;

import io.restassured.http.ContentType;
import io.restassured.response.Response;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

public class CommitBoostGenerateProxyKeyAcceptanceTest extends AcceptanceTestBase {
private static final SigningRootGenerator SIGNING_ROOT_GENERATOR =
new SigningRootGenerator(getSpec(Eth2Network.MAINNET));
private final List<BLSKeyPair> consensusBlsKeys = randomBLSKeyPairs(1);

@TempDir private Path keystoreDir;
@TempDir private Path passwordDir;
// commit boost directories
@TempDir private Path commitBoostKeystoresPath;
@TempDir private Path commitBoostPasswordDir;

@BeforeEach
void setup() throws Exception {
for (final BLSKeyPair blsKeyPair : consensusBlsKeys) {
// create consensus bls keystore
KeystoreUtil.createKeystore(blsKeyPair, keystoreDir, passwordDir, KEYSTORE_PASSWORD);
}

// commit boost proxy keys password file
final Path commitBoostPasswordFile = createCommitBoostPasswordFile(commitBoostPasswordDir);

// start web3signer with keystores and commit boost parameters
final KeystoresParameters keystoresParameters =
new DefaultKeystoresParameters(keystoreDir, passwordDir, null);
final Pair<Path, Path> commitBoostParameters =
Pair.of(commitBoostKeystoresPath, commitBoostPasswordFile);

final SignerConfigurationBuilder configBuilder =
new SignerConfigurationBuilder()
.withMode("eth2")
.withNetwork("mainnet")
.withKeystoresParameters(keystoresParameters)
.withCommitBoostParameters(commitBoostParameters);

startSigner(configBuilder.build());
}

@Test
void generateCommitBoostProxyKeys() {
for (ProxyKeySignatureScheme scheme : ProxyKeySignatureScheme.values()) {
final Response response =
signer.callCommitBoostGenerateProxyKey(
consensusBlsKeys.get(0).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(consensusBlsKeys.get(0).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(delegator, signingRoot);
});
}

// verify we can get the public keys containing the generated proxy keys
final Response pubKeyResponse = signer.callCommitBoostGetPubKeys();

pubKeyResponse
.then()
.log()
.body()
.statusCode(200)
.contentType(ContentType.JSON)
.body("keys[0].consensus", equalTo(consensusBlsKeys.get(0).getPublicKey().toHexString()))
.body("keys[0].proxy_bls", hasSize(1))
.body("keys[0].proxy_ecdsa", hasSize(1));
}

private static Spec getSpec(Eth2Network eth2Network) {
final Eth2NetworkConfiguration.Builder builder = Eth2NetworkConfiguration.builder();
return builder.applyNetworkDefaults(eth2Network).build().getSpec();
}
}
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ dependencies {
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl'

testImplementation (testFixtures(project(":signing")))
testImplementation 'tech.pegasys.teku.internal:networks'
testImplementation 'io.vertx:vertx-junit5'
testImplementation 'org.assertj:assertj-core'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -143,6 +144,7 @@ public void populateRouter(final Context context) {
}
if (commitBoostApiParameters.isEnabled()) {
new CommitBoostPublicKeysRoute(context).register();
new CommitBoostGenerateProxyKeyRoute(context, commitBoostApiParameters, eth2Spec).register();
}
}

Expand Down
Loading

0 comments on commit c389a3f

Please sign in to comment.