Skip to content

Commit

Permalink
Add adapter to handle eth1 address identifiers for the eth_sign/eth_s…
Browse files Browse the repository at this point in the history
…endTransaction

Signed-off-by: Gabriel Fukushima <[email protected]>
  • Loading branch information
gfukushima committed Jul 11, 2023
1 parent 70482fc commit 405db63
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 230 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright 2023 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.eth1rpc;

import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.web3j.crypto.Keys.getAddress;
import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier;

import tech.pegasys.web3signer.core.service.jsonrpc.handlers.signing.ConfigurationChainId;
import tech.pegasys.web3signer.dsl.signer.SignerConfiguration;
import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder;
import tech.pegasys.web3signer.dsl.utils.MetadataFileHelpers;
import tech.pegasys.web3signer.signing.KeyType;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.tuweni.bytes.Bytes;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.WalletUtils;
import org.web3j.utils.Numeric;

public class Eth1RpcReloadKeysTest extends Eth1RpcAcceptanceTestBase {

protected static final String SECP_PRIVATE_KEY_1 =
"d392469474ec227b9ec4be232b402a0490045478ab621ca559d166965f0ffd32";
protected static final String SECP_PRIVATE_KEY_2 =
"2e322a5f72c525422dc275e006d5cb3954ca5e02e9610fae0ed4cc389f622f33";
private static final MetadataFileHelpers METADATA_FILE_HELPERS = new MetadataFileHelpers();

@BeforeEach
public void setup() throws URISyntaxException {
startBesu();

final SignerConfiguration web3SignerConfiguration =
new SignerConfigurationBuilder()
.withKeyStoreDirectory(keyFileTempDir)
.withMode("eth1")
.withDownstreamHttpPort(besu.ports().getHttpRpc())
.withChainIdProvider(new ConfigurationChainId(DEFAULT_CHAIN_ID))
.build();
startSigner(web3SignerConfiguration);
}

@Test
public void additionalPublicKeyAreReportedAfterReloadUsingEth1RPC() throws IOException {

final String[] prvKeys = privateKeys();

List<String> accounts = signer.jsonRpc().ethAccounts().send().getAccounts();
// Eth1RpcAcceptanceTestBase loads a key by default
assertThat(accounts).isNotEmpty();

final String[] additionalKeys = createSecpKeys(prvKeys[1]);
signer.callReload().then().statusCode(200);

// reload is async ...
Awaitility.await()
.atMost(5, SECONDS)
.until(
() -> signer.jsonRpc().ethAccounts().send().getAccounts(),
containsInAnyOrder(
ArrayUtils.addAll(
accounts.toArray(),
List.of(normaliseIdentifier(getAddress(additionalKeys[0]))).toArray())));
}

protected String[] createSecpKeys(final String... privateKeys) {
return Stream.of(privateKeys)
.map(
privateKey -> {
final ECKeyPair ecKeyPair =
ECKeyPair.create(Numeric.toBigInt(Bytes.fromHexString(privateKey).toArray()));
final String publicKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());

createSecpKey(privateKey);

return publicKey;
})
.toArray(String[]::new);
}

private void createSecpKey(final String privateKeyHexString) {
final String password = "pass";
final Bytes privateKey = Bytes.fromHexString(privateKeyHexString);
final ECKeyPair ecKeyPair = ECKeyPair.create(Numeric.toBigInt(privateKey.toArray()));
final String publicKey = Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());

final String walletFile;
try {
walletFile =
WalletUtils.generateWalletFile(password, ecKeyPair, keyFileTempDir.toFile(), false);
} catch (Exception e) {
throw new IllegalStateException("Unable to create wallet file", e);
}

METADATA_FILE_HELPERS.createKeyStoreYamlFileAt(
keyFileTempDir.resolve(publicKey + ".yaml"),
Path.of(walletFile),
password,
KeyType.SECP256K1);
}

protected String[] privateKeys() {
return new String[] {SECP_PRIVATE_KEY_1, SECP_PRIVATE_KEY_2};
}
}
14 changes: 12 additions & 2 deletions core/src/main/java/tech/pegasys/web3signer/core/Eth1Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import tech.pegasys.web3signer.signing.EthSecpArtifactSigner;
import tech.pegasys.web3signer.signing.SecpArtifactSignature;
import tech.pegasys.web3signer.signing.config.DefaultArtifactSignerProvider;
import tech.pegasys.web3signer.signing.config.SecpArtifactSignerProviderAdpater;
import tech.pegasys.web3signer.signing.config.SignerLoader;
import tech.pegasys.web3signer.signing.config.metadata.Secp256k1ArtifactSignerFactory;
import tech.pegasys.web3signer.signing.config.metadata.interlock.InterlockKeyProvider;
Expand Down Expand Up @@ -97,7 +98,12 @@ protected void populateRouter(final Context context) {
false))
.failureHandler(errorHandler);

addReloadHandler(router, signerProvider, context.getErrorHandler());
final ArtifactSignerProvider secpArtifactSignerProvider =
new SecpArtifactSignerProviderAdpater(signerProvider);

// The order of the elements in the list DO matter
addReloadHandler(
router, List.of(signerProvider, secpArtifactSignerProvider), context.getErrorHandler());

final DownstreamPathCalculator downstreamPathCalculator =
new DownstreamPathCalculator(eth1Config.getDownstreamHttpPath());
Expand All @@ -120,7 +126,8 @@ protected void populateRouter(final Context context) {
new PassThroughHandler(transmitterFactory, jsonDecoder);

final RequestMapper requestMapper =
createRequestMapper(transmitterFactory, signerProvider, jsonDecoder, secpSigner);
createRequestMapper(
transmitterFactory, secpArtifactSignerProvider, jsonDecoder, secpSigner);

router
.route(HttpMethod.POST, ROOT_PATH)
Expand All @@ -144,6 +151,7 @@ protected ArtifactSignerProvider createArtifactSignerProvider(
try (final InterlockKeyProvider interlockKeyProvider = new InterlockKeyProvider(vertx);
final YubiHsmOpaqueDataProvider yubiHsmOpaqueDataProvider =
new YubiHsmOpaqueDataProvider()) {

final Secp256k1ArtifactSignerFactory ethSecpArtifactSignerFactory =
new Secp256k1ArtifactSignerFactory(
hashicorpConnectionFactory,
Expand Down Expand Up @@ -190,6 +198,8 @@ private RequestMapper createRequestMapper(

final TransactionFactory transactionFactory =
new TransactionFactory(jsonDecoder, transmitterFactory);

signerProvider.load();
final SendTransactionHandler sendTransactionHandler =
new SendTransactionHandler(chainId, signerProvider, transactionFactory, transmitterFactory);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ private void registerEth2Routes(
false))
.failureHandler(errorHandler);

addReloadHandler(router, blsSignerProvider, errorHandler);
addReloadHandler(router, List.of(blsSignerProvider), errorHandler);

if (isKeyManagerApiEnabled) {
router
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public FilecoinRunner(final BaseConfig baseConfig, final FilecoinNetwork network
@Override
protected void populateRouter(final Context context) {
addReloadHandler(
context.getRouter(), context.getArtifactSignerProvider(), context.getErrorHandler());
context.getRouter(),
List.of(context.getArtifactSignerProvider()),
context.getErrorHandler());

registerFilecoinJsonRpcRoute(
context.getRouter(), context.getMetricsSystem(), context.getArtifactSignerProvider());
Expand Down
10 changes: 4 additions & 6 deletions core/src/main/java/tech/pegasys/web3signer/core/Runner.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import tech.pegasys.web3signer.core.service.http.SwaggerUIRoute;
import tech.pegasys.web3signer.core.service.http.handlers.LogErrorHandler;
import tech.pegasys.web3signer.core.service.http.handlers.PublicKeysListHandler;
import tech.pegasys.web3signer.core.service.http.handlers.ReloadHandler;
import tech.pegasys.web3signer.core.service.http.handlers.UpcheckHandler;
import tech.pegasys.web3signer.core.util.FileUtil;
import tech.pegasys.web3signer.signing.ArtifactSignerProvider;
Expand All @@ -34,6 +35,7 @@
import java.io.IOException;
import java.nio.file.AccessDeniedException;
import java.nio.file.NoSuchFileException;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.StringJoiner;
Expand Down Expand Up @@ -237,16 +239,12 @@ protected void addPublicKeysListHandler(

protected void addReloadHandler(
final Router router,
final ArtifactSignerProvider artifactSignerProvider,
final List<ArtifactSignerProvider> artifactSignerProvider,
final LogErrorHandler errorHandler) {
router
.route(HttpMethod.POST, RELOAD_PATH)
.produces(JSON)
.handler(
routingContext -> {
artifactSignerProvider.load();
routingContext.response().setStatusCode(200).end();
})
.handler(new ReloadHandler(artifactSignerProvider))
.failureHandler(errorHandler);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2023 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;

import tech.pegasys.web3signer.signing.ArtifactSignerProvider;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;

public class ReloadHandler implements Handler<RoutingContext> {
List<ArtifactSignerProvider> artifactSignerProvider;

public ReloadHandler(List<ArtifactSignerProvider> artifactSignerProvider) {
this.artifactSignerProvider = artifactSignerProvider;
}

@Override
public void handle(RoutingContext routingContext) {

Executors.newSingleThreadExecutor()
.submit(
() ->
artifactSignerProvider.stream()
.forEachOrdered(
signer -> {
try {
signer.load().get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}));
routingContext.response().setStatusCode(200).end();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*/
package tech.pegasys.web3signer.core.service.jsonrpc.handlers;

import tech.pegasys.web3signer.core.Eth1AddressSignerIdentifier;
import tech.pegasys.web3signer.core.service.jsonrpc.JsonRpcRequest;
import tech.pegasys.web3signer.core.service.jsonrpc.exceptions.JsonRpcException;
import tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError;
Expand Down Expand Up @@ -44,11 +43,7 @@ public List<String> createResponseResult(final JsonRpcRequest jsonRpcRequest) {
throw new JsonRpcException(JsonRpcError.INVALID_PARAMS);
}

return publicKeySupplier.get().stream()
.map(Eth1AddressSignerIdentifier::fromPublicKey)
.map(signerIdentifier -> "0x" + signerIdentifier.toStringIdentifier())
.sorted()
.collect(Collectors.toList());
return publicKeySupplier.get().stream().sorted().collect(Collectors.toList());
}

private boolean isPopulated(final Object params) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package tech.pegasys.web3signer.core.service.jsonrpc.handlers.internalresponse;

import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.INVALID_PARAMS;
import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT;
import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier;

import tech.pegasys.web3signer.core.service.jsonrpc.EthSendTransactionJsonParameters;
import tech.pegasys.web3signer.core.service.jsonrpc.JsonDecoder;
Expand All @@ -22,9 +24,11 @@
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.ArtifactSigner;
import tech.pegasys.web3signer.signing.ArtifactSignerProvider;

import java.util.List;
import java.util.Optional;

import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;
Expand Down Expand Up @@ -71,8 +75,16 @@ public String createResponseResult(final JsonRpcRequest request) {

LOG.debug("Obtaining signer for {}", transaction.sender());

Optional<ArtifactSigner> signer =
signerProvider.getSigner(normaliseIdentifier(transaction.sender()));

if (signer.isEmpty()) {
LOG.debug("From address ({}) does not match any available account", transaction.sender());
throw new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT);
}

final TransactionSerializer transactionSerializer =
new TransactionSerializer(signerProvider, chainId);
new TransactionSerializer(signer.get(), chainId);
return transactionSerializer.serialize(transaction);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.INVALID_PARAMS;
import static tech.pegasys.web3signer.core.service.jsonrpc.response.JsonRpcError.SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT;
import static tech.pegasys.web3signer.core.util.Eth1AddressUtil.signerPublicKeyFromAddress;
import static tech.pegasys.web3signer.core.util.ResponseCodeSelector.jsonRPCErrorCode;
import static tech.pegasys.web3signer.signing.util.IdentifierUtils.normaliseIdentifier;

import tech.pegasys.web3signer.core.service.VertxRequestTransmitterFactory;
import tech.pegasys.web3signer.core.service.jsonrpc.JsonRpcRequest;
Expand All @@ -25,6 +25,7 @@
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.ArtifactSigner;
import tech.pegasys.web3signer.signing.ArtifactSignerProvider;

import java.util.Optional;
Expand Down Expand Up @@ -77,22 +78,23 @@ public void handle(final RoutingContext context, final JsonRpcRequest request) {
return;
}

Optional<String> publicKey = signerPublicKeyFromAddress(signerProvider, transaction.sender());
Optional<ArtifactSigner> signer =
signerProvider.getSigner(normaliseIdentifier(transaction.sender()));

if (publicKey.isEmpty()) {
if (signer.isEmpty()) {
LOG.debug("From address ({}) does not match any available account", transaction.sender());
context.fail(
BAD_REQUEST.code(), new JsonRpcException(SIGNING_FROM_IS_NOT_AN_UNLOCKED_ACCOUNT));
return;
}

sendTransaction(transaction, context, signerProvider, request);
sendTransaction(transaction, context, signer.get(), request);
}

private void sendTransaction(
final Transaction transaction,
final RoutingContext routingContext,
final ArtifactSignerProvider signerProvider,
final ArtifactSigner signerProvider,
final JsonRpcRequest request) {

final TransactionSerializer transactionSerializer =
Expand Down
Loading

0 comments on commit 405db63

Please sign in to comment.