Added SECP256R1 support to CLI sub command generate-blockchain-config (#2183)

Signed-off-by: Daniel Lehrner <daniel@io.builders>
pull/2196/head
Daniel Lehrner 4 years ago committed by GitHub
parent 8267f90fdd
commit 456981ccd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  2. 38
      besu/src/main/java/org/hyperledger/besu/cli/subcommands/operator/GenerateBlockchainConfig.java
  3. 4
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  4. 140
      besu/src/test/java/org/hyperledger/besu/cli/operator/OperatorSubCommandTest.java
  5. 40
      besu/src/test/resources/operator/config_generate_keys_ec_invalid.json
  6. 40
      besu/src/test/resources/operator/config_generate_keys_secp256r1.json
  7. 4
      besu/src/test/resources/operator/config_import_keys.json
  8. 42
      besu/src/test/resources/operator/config_import_keys_secp256r1.json
  9. 42
      besu/src/test/resources/operator/config_import_keys_secp256r1_invalid_keys.json
  10. 11
      crypto/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java
  11. 2
      crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java
  12. 16
      crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java
  13. 4
      crypto/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java

@ -2673,7 +2673,11 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
try {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get()));
} catch (IllegalArgumentException e) {
throw new CommandLine.InitializationException(e.getMessage());
throw new CommandLine.InitializationException(
new StringBuilder()
.append("Invalid genesis file configuration for ecCurve. ")
.append(e.getMessage())
.toString());
}
}

@ -18,6 +18,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
import org.hyperledger.besu.cli.DefaultCommandValues;
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.config.GenesisConfigOptions;
import org.hyperledger.besu.config.JsonGenesisConfigOptions;
import org.hyperledger.besu.config.JsonUtil;
import org.hyperledger.besu.consensus.ibft.IbftExtraDataCodec;
@ -26,6 +28,7 @@ import org.hyperledger.besu.crypto.SECPPrivateKey;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.crypto.SignatureAlgorithmType;
import org.hyperledger.besu.ethereum.core.Address;
import org.hyperledger.besu.ethereum.core.Util;
@ -37,6 +40,7 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.IntStream;
@ -60,7 +64,7 @@ import picocli.CommandLine.ParentCommand;
class GenerateBlockchainConfig implements Runnable {
private static final Logger LOG = LogManager.getLogger();
private static final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
private final Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
@Option(
@ -133,6 +137,7 @@ class GenerateBlockchainConfig implements Runnable {
try {
handleOutputDirectory();
parseConfig();
processEcCurve();
if (generateNodesKeys) {
generateNodesKeys();
} else {
@ -167,6 +172,16 @@ class GenerateBlockchainConfig implements Runnable {
try {
final SECPPublicKey publicKey =
SIGNATURE_ALGORITHM.get().createPublicKey(Bytes.fromHexString(publicKeyText));
if (!SIGNATURE_ALGORITHM.get().isValidPublicKey(publicKey)) {
throw new IllegalArgumentException(
new StringBuilder()
.append(publicKeyText)
.append(" is not a valid public key for elliptic curve ")
.append(SIGNATURE_ALGORITHM.get().getCurveName())
.toString());
}
writeKeypair(publicKey, null);
LOG.info("Public key imported from configuration.({})", publicKey.toString());
} catch (final IOException e) {
@ -256,6 +271,27 @@ class GenerateBlockchainConfig implements Runnable {
generateNodesKeys = JsonUtil.getBoolean(nodesConfig, "generate", false);
}
/** Sets the selected signature algorithm instance in SignatureAlgorithmFactory. */
private void processEcCurve() {
GenesisConfigOptions options = GenesisConfigFile.fromConfig(genesisConfig).getConfigOptions();
Optional<String> ecCurve = options.getEcCurve();
if (ecCurve.isEmpty()) {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.createDefault());
return;
}
try {
SignatureAlgorithmFactory.setInstance(SignatureAlgorithmType.create(ecCurve.get()));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
new StringBuilder()
.append("Invalid parameter for ecCurve in genesis config: ")
.append(e.getMessage())
.toString());
}
}
/**
* Checks if the output directory exists.
*

@ -4328,8 +4328,8 @@ public class BesuCommandTest extends CommandTestAbstract {
assertThat(commandOutput.toString()).isEmpty();
assertThat(commandErrorOutput.toString())
.contains(
"Invalid genesis file configuration. "
+ "Elliptic curve (ecCurve) abcd is not in the list of valid elliptic curves [secp256k1, secp256r1]");
"Invalid genesis file configuration for ecCurve. "
+ "abcd is not in the list of valid elliptic curves [secp256k1, secp256r1]");
}
@Test

@ -19,7 +19,6 @@ import static java.lang.System.currentTimeMillis;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.createTempDirectory;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -28,22 +27,31 @@ import static org.hyperledger.besu.cli.operator.OperatorSubCommandTest.Cmd.cmd;
import org.hyperledger.besu.cli.CommandTestAbstract;
import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.crypto.SECP256R1;
import org.hyperledger.besu.crypto.SECPPrivateKey;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
import java.util.Optional;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.json.JsonObject;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.Test;
import picocli.CommandLine;
@ -72,6 +80,7 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
@Before
public void init() throws IOException {
SignatureAlgorithmFactory.resetInstance();
tmpOutputDirectoryPath = createTempDirectory(format("output-%d", currentTimeMillis()));
}
@ -107,7 +116,8 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv"));
asList("key.pub", "key.priv"),
Optional.of(new SECP256K1()));
}
@Test
@ -129,7 +139,8 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
tmpOutputDirectoryPath,
"option.json",
true,
asList("key.pub", "key.priv"));
asList("key.pub", "key.priv"),
Optional.of(new SECP256K1()));
}
@Test
@ -140,7 +151,8 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("pub.test", "key.priv"));
asList("pub.test", "key.priv"),
Optional.of(new SECP256K1()));
}
@Test
@ -151,7 +163,8 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "priv.test"));
asList("key.pub", "priv.test"),
Optional.of(new SECP256K1()));
}
@Test
@ -197,6 +210,59 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
asList("key.pub", "key.priv"));
}
@Test
public void shouldFailIfInvalidEcCurveIsSet() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput(
cmd(),
"/operator/config_generate_keys_ec_invalid.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "priv.test")))
.isInstanceOf(CommandLine.ExecutionException.class);
}
@Test
public void shouldGenerateSECP256R1KeysWhenSetAsEcCurve() throws IOException {
runCmdAndCheckOutput(
cmd(),
"/operator/config_generate_keys_secp256r1.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv"),
Optional.of(new SECP256R1()));
}
@Test
public void shouldFailIfImportedKeysAreFromDifferentEllipticCurve() {
assertThatThrownBy(
() ->
runCmdAndCheckOutput(
cmd(),
"/operator/config_import_keys_secp256r1_invalid_keys.json",
tmpOutputDirectoryPath,
"genesis.json",
true,
asList("key.pub", "key.priv")))
.isInstanceOf(CommandLine.ExecutionException.class)
.hasMessageEndingWith(
"0xb295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680 is not a valid public key for elliptic curve secp256r1");
}
@Test
public void shouldImportSecp256R1Keys() throws IOException {
runCmdAndCheckOutput(
cmd(),
"/operator/config_import_keys_secp256r1.json",
tmpOutputDirectoryPath,
"genesis.json",
false,
singletonList("key.pub"));
}
private void runCmdAndCheckOutput(
final Cmd cmd,
final String configFile,
@ -205,6 +271,25 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
final boolean generate,
final Collection<String> expectedKeyFiles)
throws IOException {
runCmdAndCheckOutput(
cmd,
configFile,
outputDirectoryPath,
genesisFileName,
generate,
expectedKeyFiles,
Optional.empty());
}
private void runCmdAndCheckOutput(
final Cmd cmd,
final String configFile,
final Path outputDirectoryPath,
final String genesisFileName,
final boolean generate,
final Collection<String> expectedKeyFiles,
final Optional<SignatureAlgorithm> signatureAlgorithm)
throws IOException {
final URL configFilePath = this.getClass().getResource(configFile);
parseCommand(
cmd(
@ -238,10 +323,47 @@ public class OperatorSubCommandTest extends CommandTestAbstract {
final int nodeCount = jsonNode.get("blockchain").get("nodes").get("count").asInt();
assertThat(nodeCount).isEqualTo(nodesKeysFolders.length);
}
final Stream<File> nodesKeysFoldersStream = stream(nodesKeysFolders);
nodesKeysFoldersStream.forEach(
nodeFolder -> assertThat(nodeFolder.list()).containsAll(expectedKeyFiles));
for (File nodeFolder : nodesKeysFolders) {
assertThat(nodeFolder.list()).containsAll(expectedKeyFiles);
if (signatureAlgorithm.isPresent()) {
checkPublicKey(nodeFolder, signatureAlgorithm.get());
}
}
}
private void checkPublicKey(final File dir, final SignatureAlgorithm signatureAlgorithm)
throws IOException {
String publicKeyHex = readPubFile(dir);
String privateKeyHex = readPrivFile(dir);
SECPPrivateKey privateKey =
signatureAlgorithm.createPrivateKey(Bytes32.fromHexString(privateKeyHex));
SECPPublicKey expectedPublicKey = signatureAlgorithm.createPublicKey(privateKey);
assertThat(publicKeyHex).isEqualTo(expectedPublicKey.getEncodedBytes().toHexString());
}
private String readPubFile(final File dir) throws IOException {
FilenameFilter pubFilter = (folder, name) -> name.contains("pub");
return readFile(dir, pubFilter);
}
private String readPrivFile(final File dir) throws IOException {
FilenameFilter privFilter = (folder, name) -> name.contains("priv");
return readFile(dir, privFilter);
}
private String readFile(final File dir, final FilenameFilter fileFilter) throws IOException {
File[] files = dir.listFiles(fileFilter);
assertThat(files).isNotNull();
assertThat(files.length).isEqualTo(1);
return Files.readString(Path.of(files[0].getAbsolutePath()));
}
static class Cmd {

@ -0,0 +1,40 @@
{
"genesis": {
"config": {
"chainId": 2017,
"eip150Block": 0,
"ecCurve": "abcd",
"ibft2": {
}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"generate": true,
"count": 4
}
}
}

@ -0,0 +1,40 @@
{
"genesis": {
"config": {
"chainId": 2017,
"eip150Block": 0,
"ecCurve": "secp256r1",
"ibft2": {
}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"generate": true,
"count": 4
}
}
}

@ -33,8 +33,8 @@
"blockchain": {
"nodes": {
"keys": [
"0xb295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680",
"0x6295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680"
"0x9481ee2b34196827e8c4807ae29c2675c15d3641e482ab81cc0bc3ef535755b426b9f5c474213df28a563fab0bf0830b455ee8955b3faac93075422a6abf160d",
"0xf9dff57fc0bae7cba6577d82caf40cf6d266afc2a212775eb45c2188ab30aeed01e724daf4203649d4aa7e30717bfeeaf0b4c30b5d651bad39bcae8429759889"
]
}
}

@ -0,0 +1,42 @@
{
"genesis": {
"config": {
"chainId": 2017,
"petersburgBlock": 0,
"ecCurve": "secp256r1",
"ibft2": {
}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"keys": [
"0x69b48fbfeaf1275afb2773b8b2ce485660e8d6b92f74b55814a567189339dab9ec75726c13206fdf716bb107eb52666a95b7d8188af60cd1bdb66493959ec3b6",
"0x6256fbfe4731d350d35261d2fa720cc2883d4f6d7f43797e5fee0c6b4c29ea6fc16abb3a8b30384d443f45eb7099b71bf4a61128a03b80c376e3883c8d0ffd0d"
]
}
}
}

@ -0,0 +1,42 @@
{
"genesis": {
"config": {
"chainId": 2017,
"petersburgBlock": 0,
"ecCurve": "secp256r1",
"ibft2": {
}
},
"nonce": "0x0",
"timestamp": "0x5b3c3d18",
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x63746963616c2062797a616e74696e65206661756c7420746f6c6572616e6365",
"coinbase": "0x0000000000000000000000000000000000000000",
"ibft2": {
"blockperiodseconds": 2,
"epochlength": 30000,
"requesttimeoutseconds": 10
},
"alloc": {
"24defc2d149861d3d245749b81fe0e6b28e04f31": {
"balance": "0x446c3b15f9926687d2c40534fdb564000000000000"
},
"2a813d7db3de19b07f92268b6d4125ed295cbe00": {
"balance": "0x446c3b15f9926687d2c40534fdb542000000000000"
}
}
},
"blockchain": {
"nodes": {
"keys": [
"0xb295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680",
"0x6295c4242fb40c6e8ac7b831c916846050f191adc560b8098ba6ad513079571ec1be6e5e1a715857a13a91963097962e048c36c5863014b59e8f67ed3f667680"
]
}
}
}

@ -174,6 +174,17 @@ public abstract class AbstractSECP256 implements SignatureAlgorithm {
return publicKey.asEcPoint(curve);
}
@Override
public boolean isValidPublicKey(final SECPPublicKey publicKey) {
try {
publicKeyAsEcPoint(publicKey);
} catch (IllegalArgumentException e) {
return false;
}
return true;
}
@Override
public KeyPair createKeyPair(final SECPPrivateKey privateKey) {
return KeyPair.create(privateKey, curve, ALGORITHM);

@ -67,6 +67,8 @@ public interface SignatureAlgorithm {
ECPoint publicKeyAsEcPoint(final SECPPublicKey publicKey);
boolean isValidPublicKey(SECPPublicKey publicKey);
KeyPair createKeyPair(final SECPPrivateKey privateKey);
KeyPair generateKeyPair();

@ -38,13 +38,7 @@ public class SignatureAlgorithmType {
public static SignatureAlgorithmType create(final String ecCurve)
throws IllegalArgumentException {
if (!isValidType(ecCurve)) {
throw new IllegalArgumentException(
new StringBuilder()
.append("Invalid genesis file configuration. Elliptic curve (ecCurve) ")
.append(ecCurve)
.append(" is not in the list of valid elliptic curves ")
.append(getEcCurvesListAsString())
.toString());
throw new IllegalArgumentException(invalidTypeErrorMessage(ecCurve));
}
return new SignatureAlgorithmType(SUPPORTED_ALGORITHMS.get(ecCurve));
@ -66,6 +60,14 @@ public class SignatureAlgorithmType {
return signatureAlgorithm.getCurveName().equals(DEFAULT_EC_CURVE_NAME);
}
private static String invalidTypeErrorMessage(final String invalidEcCurve) {
return new StringBuilder()
.append(invalidEcCurve)
.append(" is not in the list of valid elliptic curves ")
.append(getEcCurvesListAsString())
.toString();
}
private static String getEcCurvesListAsString() {
Iterator<Map.Entry<String, Supplier<SignatureAlgorithm>>> it =
SUPPORTED_ALGORITHMS.entrySet().iterator();

@ -31,8 +31,6 @@ public class SignatureAlgorithmTypeTest {
@Test
public void shouldThrowExceptionWhenInvalidParameterIsGiven() {
assertThatThrownBy(() -> SignatureAlgorithmType.create("abcd"))
.hasMessage(
"Invalid genesis file configuration. Elliptic curve (ecCurve) abcd is not in the list"
+ " of valid elliptic curves [secp256k1, secp256r1]");
.hasMessage("abcd is not in the list of valid elliptic curves [secp256k1, secp256r1]");
}
}

Loading…
Cancel
Save