Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Commit boost API - Generate Proxy Key #1043

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading