Skip to content

Commit

Permalink
Add SECP256R1 support (hyperledger#2008)
Browse files Browse the repository at this point in the history
Add SECP256R1 support

Signed-off-by: Daniel Lehrner <daniel@io.builders>
  • Loading branch information
daniel-iobuilders authored and eum602 committed Nov 3, 2023
1 parent df6912a commit 8a178a4
Show file tree
Hide file tree
Showing 20 changed files with 839 additions and 346 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.hyperledger.besu.cli.config.NetworkName;
import org.hyperledger.besu.crypto.KeyPair;
import org.hyperledger.besu.crypto.KeyPairUtil;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.websocket.WebSocketConfiguration;
import org.hyperledger.besu.ethereum.core.Address;
Expand All @@ -35,6 +36,7 @@
import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.genesis.GenesisConfigurationProvider;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.admin.AdminRequestFactory;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.BftRequestFactory;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.bft.ConsensusType;
Expand Down Expand Up @@ -691,6 +693,13 @@ public <T> T execute(final Transaction<T> transaction) {
return transaction.execute(nodeRequests());
}

@Override
public <T> T execute(
final TransactionWithSignatureAlgorithm<T> transaction,
final SignatureAlgorithm signatureAlgorithm) {
return transaction.execute(nodeRequests(), signatureAlgorithm);
}

@Override
public void verify(final Condition expected) {
expected.verify(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.node;

import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.condition.Condition;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;

public interface Node {

<T> T execute(Transaction<T> transaction);

<T> T execute(
TransactionWithSignatureAlgorithm<T> transaction, SignatureAlgorithm signatureAlgorithm);

void verify(final Condition expected);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
import static java.util.Arrays.asList;
import static java.util.stream.Collectors.toList;

import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.crypto.SignatureAlgorithmType;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
Expand All @@ -38,6 +41,7 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
Expand All @@ -50,6 +54,8 @@ public class BesuNodeFactory {
private final NodeConfigurationFactory node = new NodeConfigurationFactory();

public BesuNode create(final BesuNodeConfiguration config) throws IOException {
instantiateSignatureAlgorithmFactory(config);

return new BesuNode(
config.getName(),
config.getDataPath(),
Expand Down Expand Up @@ -458,4 +464,32 @@ public BesuNode createNodeWithStaticNodes(final String name, final List<Node> st
public BesuNode runCommand(final String command) throws IOException {
return create(new BesuNodeConfigurationBuilder().name("run " + command).run(command).build());
}

private void instantiateSignatureAlgorithmFactory(final BesuNodeConfiguration config) {
if (SignatureAlgorithmFactory.isInstanceSet()) {
return;
}

Optional<String> ecCurve = getEcCurveFromGenesisFile(config);

if (ecCurve.isEmpty()) {
SignatureAlgorithmFactory.setDefaultInstance();
return;
}

SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get()));
}

private Optional<String> getEcCurveFromGenesisFile(final BesuNodeConfiguration config) {
Optional<String> genesisConfig =
config.getGenesisConfigProvider().create(Collections.emptyList());

if (genesisConfig.isEmpty()) {
return Optional.empty();
}

GenesisConfigFile genesisConfigFile = GenesisConfigFile.fromConfig(genesisConfig.get());

return genesisConfigFile.getConfigOptions().getEcCurve();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction;

import org.hyperledger.besu.crypto.SECPPrivateKey;
import org.hyperledger.besu.crypto.SECPSignature;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;

import java.util.List;

import org.apache.tuweni.bytes.Bytes32;
import org.web3j.crypto.Credentials;
import org.web3j.crypto.RawTransaction;
import org.web3j.crypto.Sign;
import org.web3j.crypto.TransactionEncoder;
import org.web3j.rlp.RlpEncoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpType;
import org.web3j.utils.Numeric;

public class SignUtil {

private SignUtil() {}

public static String signTransaction(
final RawTransaction transaction,
final Account sender,
final SignatureAlgorithm signatureAlgorithm) {
byte[] encodedTransaction = TransactionEncoder.encode(transaction);

Credentials credentials = sender.web3jCredentialsOrThrow();
SECPPrivateKey privateKey =
signatureAlgorithm.createPrivateKey(credentials.getEcKeyPair().getPrivateKey());

byte[] transactionHash = org.web3j.crypto.Hash.sha3(encodedTransaction);

SECPSignature secpSignature =
signatureAlgorithm.sign(
Bytes32.wrap(transactionHash), signatureAlgorithm.createKeyPair(privateKey));

Sign.SignatureData signature =
new Sign.SignatureData(
// In Ethereum transaction 27 is added to recId (v)
// See https://ethereum.github.io/yellowpaper/paper.pdf
// Appendix F. Signing Transactions (281)
(byte) (secpSignature.getRecId() + 27),
secpSignature.getR().toByteArray(),
secpSignature.getS().toByteArray());
List<RlpType> values = TransactionEncoder.asRlpValues(transaction, signature);
RlpList rlpList = new RlpList(values);
final byte[] encodedSignedTransaction = RlpEncoder.encode(rlpList);

return Numeric.toHexString(encodedSignedTransaction);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction;

import org.hyperledger.besu.crypto.SignatureAlgorithm;

@FunctionalInterface
public interface TransactionWithSignatureAlgorithm<T> {
T execute(final NodeRequests node, final SignatureAlgorithm signatureAlgorithm);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@
*/
package org.hyperledger.besu.tests.acceptance.dsl.transaction.account;

import static org.web3j.utils.Numeric.toHexString;

import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.blockchain.Amount;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.NodeRequests;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.SignUtil;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.Transaction;
import org.hyperledger.besu.tests.acceptance.dsl.transaction.TransactionWithSignatureAlgorithm;

import java.io.IOException;
import java.math.BigDecimal;
Expand All @@ -31,8 +32,10 @@
import org.web3j.crypto.TransactionEncoder;
import org.web3j.utils.Convert;
import org.web3j.utils.Convert.Unit;
import org.web3j.utils.Numeric;

public class TransferTransaction implements Transaction<Hash> {
public class TransferTransaction
implements Transaction<Hash>, TransactionWithSignatureAlgorithm<Hash> {

/** Price for each for each GAS units in this transaction (wei). */
private static final BigInteger MINIMUM_GAS_PRICE = BigInteger.valueOf(1000);
Expand Down Expand Up @@ -64,33 +67,41 @@ public TransferTransaction(
@Override
public Hash execute(final NodeRequests node) {
final String signedTransactionData = signedTransactionData();
try {
return Hash.fromHexString(
node.eth().ethSendRawTransaction(signedTransactionData).send().getTransactionHash());
} catch (final IOException e) {
throw new RuntimeException(e);
}
return sendRawTransaction(node, signedTransactionData);
}

@Override
public Hash execute(final NodeRequests node, final SignatureAlgorithm signatureAlgorithm) {
final String signedTransactionData =
signedTransactionDataWithSignatureAlgorithm(signatureAlgorithm);
return sendRawTransaction(node, signedTransactionData);
}

public Amount executionCost() {
return Amount.wei(INTRINSIC_GAS.multiply(gasPrice));
}

public String signedTransactionData() {
final Optional<BigInteger> nonce = getNonce();

final RawTransaction transaction =
RawTransaction.createEtherTransaction(
nonce.orElse(nonce.orElseGet(sender::getNextNonce)),
gasPrice,
INTRINSIC_GAS,
recipient.getAddress(),
Convert.toWei(transferAmount, transferUnit).toBigIntegerExact());
final RawTransaction transaction = createRawTransaction();

return toHexString(
return Numeric.toHexString(
TransactionEncoder.signMessage(transaction, sender.web3jCredentialsOrThrow()));
}

private String signedTransactionDataWithSignatureAlgorithm(
final SignatureAlgorithm signatureAlgorithm) {
return SignUtil.signTransaction(createRawTransaction(), sender, signatureAlgorithm);
}

private Hash sendRawTransaction(final NodeRequests node, final String signedTransactionData) {
try {
return Hash.fromHexString(
node.eth().ethSendRawTransaction(signedTransactionData).send().getTransactionHash());
} catch (final IOException e) {
throw new RuntimeException(e);
}
}

private Optional<BigInteger> getNonce() {
return nonce == null ? Optional.empty() : Optional.of(nonce);
}
Expand All @@ -108,4 +119,15 @@ private BigInteger convertGasPriceToWei(final Amount unconverted) {

return price;
}

private RawTransaction createRawTransaction() {
final Optional<BigInteger> nonce = getNonce();

return RawTransaction.createEtherTransaction(
nonce.orElse(nonce.orElseGet(sender::getNextNonce)),
gasPrice,
INTRINSIC_GAS,
recipient.getAddress(),
Convert.toWei(transferAmount, transferUnit).toBigIntegerExact());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 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.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
package org.hyperledger.besu.tests.acceptance.crypto;

import static org.assertj.core.api.Assertions.assertThat;

import org.hyperledger.besu.crypto.SECP256R1;
import org.hyperledger.besu.ethereum.core.Hash;
import org.hyperledger.besu.tests.acceptance.dsl.AcceptanceTestBase;
import org.hyperledger.besu.tests.acceptance.dsl.account.Account;
import org.hyperledger.besu.tests.acceptance.dsl.node.Node;

import org.junit.Before;
import org.junit.Test;

public class SECP256R1AcceptanceTest extends AcceptanceTestBase {
private Node minerNode;
private Node fullNode;

protected static final String GENESIS_FILE = "/crypto/secp256r1.json";

@Before
public void setUp() throws Exception {
minerNode = besu.createCustomGenesisNode("node1", GENESIS_FILE, true, true);
fullNode = besu.createCustomGenesisNode("node2", GENESIS_FILE, false);
cluster.start(minerNode, fullNode);
}

@Test
public void shouldConnectToOtherPeer() {
minerNode.verify(net.awaitPeerCount(1));
fullNode.verify(net.awaitPeerCount(1));
}

@Test
public void transactionShouldBeSuccessful() {
final Account recipient = accounts.createAccount("recipient");

final Hash transactionHash =
minerNode.execute(accountTransactions.createTransfer(recipient, 5), new SECP256R1());
assertThat(transactionHash).isNotNull();
cluster.verify(recipient.balanceEquals(5));
}
}
23 changes: 23 additions & 0 deletions acceptance-tests/tests/src/test/resources/crypto/secp256r1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"config": {
"chainId": 1981,
"ecCurve": "secp256r1",
"constantinoplefixblock": 0,
"ethash": {
"fixeddifficulty": 1000
}
},
"nonce": "0x0",
"timestamp": "0x58ee40ba",
"gasLimit": "0x1fffffffffffff",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"alloc": {
"91240f5b6994c7ed80f9f94b1aa847880ad3b150": {
"privateKey": "8f2a55949038a9610f50fb23b5883af3b4ecb3c3bb792cbcefbd1542c692be63",
"info": "This genesis file uses SECP256R1 as elliptic curve. The address is only valid for this curve and invalid with the default SECP256K1 curve.",
"comment": "private key and this comment are ignored. In a real chain, the private key should NOT be stored",
"balance": "0xad78ebc5ac6200000"
}
}
}
Loading

0 comments on commit 8a178a4

Please sign in to comment.