Add SECP256R1 support (#2008)

Add SECP256R1 support

Signed-off-by: Daniel Lehrner <daniel@io.builders>
pull/2183/head
Daniel Lehrner 4 years ago committed by GitHub
parent 877e582c6b
commit d7edb37c39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/BesuNode.java
  2. 5
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/Node.java
  3. 34
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/configuration/BesuNodeFactory.java
  4. 68
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/SignUtil.java
  5. 23
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/TransactionWithSignatureAlgorithm.java
  6. 60
      acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/transaction/account/TransferTransaction.java
  7. 57
      acceptance-tests/tests/src/test/java/org/hyperledger/besu/tests/acceptance/crypto/SECP256R1AcceptanceTest.java
  8. 23
      acceptance-tests/tests/src/test/resources/crypto/secp256r1.json
  9. 2
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  10. 2
      besu/src/test/java/org/hyperledger/besu/cli/BesuCommandTest.java
  11. 370
      crypto/src/main/java/org/hyperledger/besu/crypto/AbstractSECP256.java
  12. 323
      crypto/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java
  13. 39
      crypto/src/main/java/org/hyperledger/besu/crypto/SECP256R1.java
  14. 2
      crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithm.java
  15. 13
      crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmFactory.java
  16. 13
      crypto/src/main/java/org/hyperledger/besu/crypto/SignatureAlgorithmType.java
  17. 1
      crypto/src/test/java/org/hyperledger/besu/crypto/SECP256K1Test.java
  18. 126
      crypto/src/test/java/org/hyperledger/besu/crypto/SECP256R1Test.java
  19. 2
      crypto/src/test/java/org/hyperledger/besu/crypto/SignatureAlgorithmTypeTest.java
  20. 13
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/discovery/PeerDiscoveryAgent.java

@ -21,6 +21,7 @@ import static org.apache.tuweni.io.file.Files.copyResource;
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;
@ -35,6 +36,7 @@ import org.hyperledger.besu.tests.acceptance.dsl.node.configuration.NodeConfigur
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;
@ -691,6 +693,13 @@ public class BesuNode implements NodeConfiguration, RunnableNode, AutoCloseable
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);

@ -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);
}

@ -17,6 +17,9 @@ package org.hyperledger.besu.tests.acceptance.dsl.node.configuration;
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;
@ -38,6 +41,7 @@ import java.io.IOException;
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;
@ -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(),
@ -458,4 +464,32 @@ public class BesuNodeFactory {
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();
}
}

@ -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);
}
}

@ -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);
}

@ -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;
@ -31,8 +32,10 @@ import org.web3j.crypto.RawTransaction;
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);
@ -64,12 +67,14 @@ public class TransferTransaction implements Transaction<Hash> {
@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() {
@ -77,20 +82,26 @@ public class TransferTransaction implements Transaction<Hash> {
}
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);
}
@ -108,4 +119,15 @@ public class TransferTransaction implements Transaction<Hash> {
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());
}
}

@ -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));
}
}

@ -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"
}
}
}

@ -1180,6 +1180,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
try {
configureLogging(true);
instantiateSignatureAlgorithmFactory();
configureNativeLibs();
logger.info("Starting Besu version: {}", BesuInfo.nodeName(identityString));
// Need to create vertx after cmdline has been parsed, such that metricsSystem is configurable
@ -1579,7 +1580,6 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
metricsConfiguration = metricsConfiguration();
logger.info("Security Module: {}", securityModuleName);
instantiateSignatureAlgorithmFactory();
return this;
}

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

@ -0,0 +1,370 @@
/*
* 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.crypto;
import static com.google.common.base.Preconditions.checkArgument;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.UnaryOperator;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;
public abstract class AbstractSECP256 implements SignatureAlgorithm {
protected static final int PRIVATE_KEY_BYTE_LENGTH = 32;
protected static final int PUBLIC_KEY_BYTE_LENGTH = 64;
protected static final int SIGNATURE_BYTE_LENGTH = 65;
public static final String PROVIDER = "BC";
protected final ECDomainParameters curve;
protected final BigInteger halfCurveOrder;
protected final KeyPairGenerator keyPairGenerator;
protected final BigInteger curveOrder;
final BigInteger prime;
protected AbstractSECP256(final String curveName, final BigInteger prime) {
this.prime = prime;
Security.addProvider(new BouncyCastleProvider());
final X9ECParameters params = SECNamedCurves.getByName(curveName);
curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
curveOrder = curve.getN();
halfCurveOrder = curveOrder.shiftRight(1);
try {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
} catch (final Exception e) {
throw new RuntimeException(e);
}
final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(curveName);
try {
keyPairGenerator.initialize(ecGenParameterSpec, SecureRandomProvider.createSecureRandom());
} catch (final InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
}
@Override
public SECPSignature normaliseSignature(
final BigInteger nativeR,
final BigInteger nativeS,
final SECPPublicKey publicKey,
final Bytes32 dataHash) {
BigInteger s = nativeS;
// Automatically adjust the S component to be less than or equal to half the curve
// order, if necessary. This is required because for every signature (r,s) the signature
// (r, -s (mod N)) is a valid signature of the same message. However, we dislike the
// ability to modify the bits of a Bitcoin transaction after it's been signed, as that
// violates various assumed invariants. Thus in future only one of those forms will be
// considered legal and the other will be banned.
if (s.compareTo(halfCurveOrder) > 0) {
// The order of the curve is the number of valid points that exist on that curve.
// If S is in the upper half of the number of valid points, then bring it back to
// the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
s = curve.getN().subtract(s);
}
// Now we have to work backwards to figure out the recId needed to recover the signature.
int recId = -1;
final BigInteger publicKeyBI = publicKey.getEncodedBytes().toUnsignedBigInteger();
for (int i = 0; i < 4; i++) {
final BigInteger k = recoverFromSignature(i, nativeR, s, dataHash);
if (k != null && k.equals(publicKeyBI)) {
recId = i;
break;
}
}
if (recId == -1) {
throw new RuntimeException(
"Could not construct a recoverable key. This should never happen.");
}
return new SECPSignature(nativeR, s, (byte) recId);
}
/**
* Calculates an ECDH key agreement between the private and the public key.
*
* @param privKey The private key.
* @param theirPubKey The public key.
* @return The agreed secret.
*/
@Override
public Bytes32 calculateECDHKeyAgreement(
final SECPPrivateKey privKey, final SECPPublicKey theirPubKey) {
checkArgument(privKey != null, "missing private key");
checkArgument(theirPubKey != null, "missing remote public key");
final ECPrivateKeyParameters privKeyP = new ECPrivateKeyParameters(privKey.getD(), curve);
final ECPublicKeyParameters pubKeyP =
new ECPublicKeyParameters(theirPubKey.asEcPoint(curve), curve);
final ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(privKeyP);
final BigInteger agreed = agreement.calculateAgreement(pubKeyP);
return UInt256.valueOf(agreed).toBytes();
}
@Override
public SECPPrivateKey createPrivateKey(final BigInteger key) {
return SECPPrivateKey.create(key, ALGORITHM);
}
@Override
public SECPPrivateKey createPrivateKey(final Bytes32 key) {
return SECPPrivateKey.create(key, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final SECPPrivateKey privateKey) {
return SECPPublicKey.create(privateKey, curve, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final BigInteger key) {
return SECPPublicKey.create(key, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final Bytes encoded) {
return SECPPublicKey.create(encoded, ALGORITHM);
}
@Override
public ECPoint publicKeyAsEcPoint(final SECPPublicKey publicKey) {
return publicKey.asEcPoint(curve);
}
@Override
public KeyPair createKeyPair(final SECPPrivateKey privateKey) {
return KeyPair.create(privateKey, curve, ALGORITHM);
}
@Override
public KeyPair generateKeyPair() {
return KeyPair.generate(keyPairGenerator, ALGORITHM);
}
@Override
public SECPSignature createSignature(final BigInteger r, final BigInteger s, final byte recId) {
return SECPSignature.create(r, s, recId, curveOrder);
}
@Override
public SECPSignature decodeSignature(final Bytes bytes) {
return SECPSignature.decode(bytes, curveOrder);
}
@Override
public BigInteger getHalfCurveOrder() {
return halfCurveOrder;
}
@Override
public String getProvider() {
return PROVIDER;
}
// Decompress a compressed public key (x co-ord and low-bit of y-coord).
protected ECPoint decompressKey(final BigInteger xBN, final boolean yBit) {
final X9IntegerConverter x9 = new X9IntegerConverter();
final byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(curve.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
// TODO: Find a better way to handle an invalid point compression here.
// Currently ECCurve#decodePoint throws an IllegalArgumentException.
return curve.getCurve().decodePoint(compEnc);
}
/**
* Given the components of a signature and a selector value, recover and return the public key
* that generated the signature according to the algorithm in SEC1v2 section 4.1.6.
*
* <p>If this method returns null it means recovery was not possible and recId should be iterated.
*
* <p>Given the above two points, a correct usage of this method is inside a for loop from 0 to 3,
* and if the output is null OR a key that is not the one you expect, you try again with the next
* recId.
*
* @param recId Which possible key to recover.
* @param r The R component of the signature.
* @param s The S component of the signature.
* @param dataHash Hash of the data that was signed.
* @return An ECKey containing only the public part, or null if recovery wasn't possible.
*/
protected BigInteger recoverFromSignature(
final int recId, final BigInteger r, final BigInteger s, final Bytes32 dataHash) {
assert (recId >= 0);
assert (r.signum() >= 0);
assert (s.signum() >= 0);
assert (dataHash != null);
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
final BigInteger n = curve.getN(); // Curve order.
final BigInteger i = BigInteger.valueOf((long) recId / 2);
final BigInteger x = r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion
// routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R
// using the conversion routine specified in Section 2.3.4. If this conversion
// routine outputs "invalid", then do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are
// two possibilities. So it's encoded in the recId.
final ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers
// responsibility).
if (!R.multiply(n).isInfinity()) {
return null;
}
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
final BigInteger e = dataHash.toUnsignedBigInteger();
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via
// iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n).
// In the above equation ** is point multiplication and + is point addition (the EC group
// operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For
// example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and
// -3 mod 11 = 8.
final BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
final BigInteger rInv = r.modInverse(n);
final BigInteger srInv = rInv.multiply(s).mod(n);
final BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
final ECPoint q = ECAlgorithms.sumOfTwoMultiplies(curve.getG(), eInvrInv, R, srInv);
if (q.isInfinity()) {
return null;
}
final byte[] qBytes = q.getEncoded(false);
// We remove the prefix
return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
}
@Override
public SECPSignature sign(final Bytes32 dataHash, final KeyPair keyPair) {
final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
final ECPrivateKeyParameters privKey =
new ECPrivateKeyParameters(
keyPair.getPrivateKey().getEncodedBytes().toUnsignedBigInteger(), curve);
signer.init(true, privKey);
final BigInteger[] components = signer.generateSignature(dataHash.toArrayUnsafe());
return normaliseSignature(components[0], components[1], keyPair.getPublicKey(), dataHash);
}
/**
* Verifies the given ECDSA signature against the message bytes using the public key bytes.
*
* <p>When using native ECDSA verification, data must be 32 bytes, and no element may be larger
* than 520 bytes.
*
* @param data Hash of the data to verify.
* @param signature ASN.1 encoded signature.
* @param pub The public key bytes to use.
* @return True if the verification is successful.
*/
@Override
public boolean verify(final Bytes data, final SECPSignature signature, final SECPPublicKey pub) {
final ECDSASigner signer = new ECDSASigner();
final Bytes toDecode = Bytes.wrap(Bytes.of((byte) 4), pub.getEncodedBytes());
final ECPublicKeyParameters params =
new ECPublicKeyParameters(curve.getCurve().decodePoint(toDecode.toArrayUnsafe()), curve);
signer.init(false, params);
try {
return signer.verifySignature(data.toArrayUnsafe(), signature.getR(), signature.getS());
} catch (final NullPointerException e) {
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those
// signatures
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
return false;
}
}
/**
* Verifies the given ECDSA signature using the public key bytes against the message bytes,
* previously passed through a preprocessor function, which is normally a hashing function.
*
* @param data The data to verify.
* @param signature ASN.1 encoded signature.
* @param pub The public key bytes to use.
* @param preprocessor The function to apply to the data before verifying the signature, normally
* a hashing function.
* @return True if the verification is successful.
*/
@Override
public boolean verify(
final Bytes data,
final SECPSignature signature,
final SECPPublicKey pub,
final UnaryOperator<Bytes> preprocessor) {
checkArgument(preprocessor != null, "preprocessor must not be null");
return verify(preprocessor.apply(data), signature, pub);
}
@Override
public Optional<SECPPublicKey> recoverPublicKeyFromSignature(
final Bytes32 dataHash, final SECPSignature signature) {
final BigInteger publicKeyBI =
recoverFromSignature(signature.getRecId(), signature.getR(), signature.getS(), dataHash);
return Optional.of(SECPPublicKey.create(publicKeyBI, ALGORITHM));
}
@Override
public Bytes compressPublicKey(final SECPPublicKey uncompressedPublicKey) {
return Bytes.wrap(publicKeyAsEcPoint(uncompressedPublicKey).getEncoded(true));
}
}

@ -14,7 +14,6 @@
*/
package org.hyperledger.besu.crypto;
import static com.google.common.base.Preconditions.checkArgument;
import static org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.SECP256K1_EC_UNCOMPRESSED;
import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1;
@ -22,15 +21,8 @@ import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.secp256k1_ecdsa_rec
import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.secp256k1_ecdsa_signature;
import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.secp256k1_pubkey;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.UnaryOperator;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
@ -38,20 +30,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.units.bigints.UInt256;
import org.bouncycastle.asn1.sec.SECNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.asn1.x9.X9IntegerConverter;
import org.bouncycastle.crypto.agreement.ECDHBasicAgreement;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.crypto.signers.HMacDSAKCalculator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.math.ec.ECAlgorithms;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
/*
@ -62,39 +40,16 @@ import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
* Adapted from the web3j (Apache 2 License) implementations:
* https://github.com/web3j/web3j/crypto/src/main/java/org/web3j/crypto/*.java
*/
public class SECP256K1 implements SignatureAlgorithm {
public class SECP256K1 extends AbstractSECP256 {
private static final Logger LOG = LogManager.getLogger();
private boolean useNative = true;
public static final String CURVE_NAME = "secp256k1";
public static final String PROVIDER = "BC";
private final ECDomainParameters curve;
private final BigInteger halfCurveOrder;
private final KeyPairGenerator keyPairGenerator;
private final BigInteger curveOrder;
public SECP256K1() {
Security.addProvider(new BouncyCastleProvider());
final X9ECParameters params = SECNamedCurves.getByName(CURVE_NAME);
curve = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
curveOrder = curve.getN();
halfCurveOrder = curveOrder.shiftRight(1);
try {
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
} catch (final Exception e) {
throw new RuntimeException(e);
}
final ECGenParameterSpec ecGenParameterSpec = new ECGenParameterSpec(CURVE_NAME);
try {
keyPairGenerator.initialize(ecGenParameterSpec, SecureRandomProvider.createSecureRandom());
} catch (final InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
}
super(CURVE_NAME, SecP256K1Curve.q);
}
@Override
@ -108,7 +63,7 @@ public class SECP256K1 implements SignatureAlgorithm {
if (useNative) {
return signNative(dataHash, keyPair);
} else {
return signDefault(dataHash, keyPair);
return super.sign(dataHash, keyPair);
}
}
@ -128,239 +83,8 @@ public class SECP256K1 implements SignatureAlgorithm {
if (useNative) {
return verifyNative(data, signature, pub);
} else {
return verifyDefault(data, signature, pub);
}
}
/** Decompress a compressed public key (x co-ord and low-bit of y-coord). */
private ECPoint decompressKey(final BigInteger xBN, final boolean yBit) {
final X9IntegerConverter x9 = new X9IntegerConverter();
final byte[] compEnc = x9.integerToBytes(xBN, 1 + x9.getByteLength(curve.getCurve()));
compEnc[0] = (byte) (yBit ? 0x03 : 0x02);
// TODO: Find a better way to handle an invalid point compression here.
// Currently ECCurve#decodePoint throws an IllegalArgumentException.
return curve.getCurve().decodePoint(compEnc);
}
/**
* Given the components of a signature and a selector value, recover and return the public key
* that generated the signature according to the algorithm in SEC1v2 section 4.1.6.
*
* <p>If this method returns null it means recovery was not possible and recId should be iterated.
*
* <p>Given the above two points, a correct usage of this method is inside a for loop from 0 to 3,
* and if the output is null OR a key that is not the one you expect, you try again with the next
* recId.
*
* @param recId Which possible key to recover.
* @param r The R component of the signature.
* @param s The S component of the signature.
* @param dataHash Hash of the data that was signed.
* @return An ECKey containing only the public part, or null if recovery wasn't possible.
*/
private BigInteger recoverFromSignature(
final int recId, final BigInteger r, final BigInteger s, final Bytes32 dataHash) {
assert (recId >= 0);
assert (r.signum() >= 0);
assert (s.signum() >= 0);
assert (dataHash != null);
// 1.0 For j from 0 to h (h == recId here and the loop is outside this function)
// 1.1 Let x = r + jn
final BigInteger n = curve.getN(); // Curve order.
final BigInteger i = BigInteger.valueOf((long) recId / 2);
final BigInteger x = r.add(i.multiply(n));
// 1.2. Convert the integer x to an octet string X of length mlen using the conversion
// routine specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉.
// 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R
// using the conversion routine specified in Section 2.3.4. If this conversion
// routine outputs "invalid", then do another iteration of Step 1.
//
// More concisely, what these points mean is to use X as a compressed public key.
final BigInteger prime = SecP256K1Curve.q;
if (x.compareTo(prime) >= 0) {
// Cannot have point co-ordinates larger than this as everything takes place modulo Q.
return null;
}
// Compressed keys require you to know an extra bit of data about the y-coord as there are
// two possibilities. So it's encoded in the recId.
final ECPoint R = decompressKey(x, (recId & 1) == 1);
// 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers
// responsibility).
if (!R.multiply(n).isInfinity()) {
return null;
}
// 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification.
final BigInteger e = dataHash.toUnsignedBigInteger();
// 1.6. For k from 1 to 2 do the following. (loop is outside this function via
// iterating recId)
// 1.6.1. Compute a candidate public key as:
// Q = mi(r) * (sR - eG)
//
// Where mi(x) is the modular multiplicative inverse. We transform this into the following:
// Q = (mi(r) * s ** R) + (mi(r) * -e ** G)
// Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n).
// In the above equation ** is point multiplication and + is point addition (the EC group
// operator).
//
// We can find the additive inverse by subtracting e from zero then taking the mod. For
// example the additive inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and
// -3 mod 11 = 8.
final BigInteger eInv = BigInteger.ZERO.subtract(e).mod(n);
final BigInteger rInv = r.modInverse(n);
final BigInteger srInv = rInv.multiply(s).mod(n);
final BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
final ECPoint q = ECAlgorithms.sumOfTwoMultiplies(curve.getG(), eInvrInv, R, srInv);
if (q.isInfinity()) {
return null;
return super.verify(data, signature, pub);
}
final byte[] qBytes = q.getEncoded(false);
// We remove the prefix
return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
}
private SECPSignature signDefault(final Bytes32 dataHash, final KeyPair keyPair) {
final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
final ECPrivateKeyParameters privKey =
new ECPrivateKeyParameters(
keyPair.getPrivateKey().getEncodedBytes().toUnsignedBigInteger(), curve);
signer.init(true, privKey);
final BigInteger[] components = signer.generateSignature(dataHash.toArrayUnsafe());
return normaliseSignature(components[0], components[1], keyPair.getPublicKey(), dataHash);
}
@Override
public SECPSignature normaliseSignature(
final BigInteger nativeR,
final BigInteger nativeS,
final SECPPublicKey publicKey,
final Bytes32 dataHash) {
BigInteger s = nativeS;
// Automatically adjust the S component to be less than or equal to half the curve
// order, if necessary. This is required because for every signature (r,s) the signature
// (r, -s (mod N)) is a valid signature of the same message. However, we dislike the
// ability to modify the bits of a Bitcoin transaction after it's been signed, as that
// violates various assumed invariants. Thus in future only one of those forms will be
// considered legal and the other will be banned.
if (s.compareTo(halfCurveOrder) > 0) {
// The order of the curve is the number of valid points that exist on that curve.
// If S is in the upper half of the number of valid points, then bring it back to
// the lower half. Otherwise, imagine that
// N = 10
// s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// 10 - 8 == 2, giving us always the latter solution, which is canonical.
s = curve.getN().subtract(s);
}
// Now we have to work backwards to figure out the recId needed to recover the signature.
int recId = -1;
final BigInteger publicKeyBI = publicKey.getEncodedBytes().toUnsignedBigInteger();
for (int i = 0; i < 4; i++) {
final BigInteger k = recoverFromSignature(i, nativeR, s, dataHash);
if (k != null && k.equals(publicKeyBI)) {
recId = i;
break;
}
}
if (recId == -1) {
throw new RuntimeException(
"Could not construct a recoverable key. This should never happen.");
}
return new SECPSignature(nativeR, s, (byte) recId);
}
private boolean verifyDefault(
final Bytes data, final SECPSignature signature, final SECPPublicKey pub) {
final ECDSASigner signer = new ECDSASigner();
final Bytes toDecode = Bytes.wrap(Bytes.of((byte) 4), pub.getEncodedBytes());
final ECPublicKeyParameters params =
new ECPublicKeyParameters(curve.getCurve().decodePoint(toDecode.toArrayUnsafe()), curve);
signer.init(false, params);
try {
return signer.verifySignature(data.toArrayUnsafe(), signature.getR(), signature.getS());
} catch (final NullPointerException e) {
// Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those
// signatures
// are inherently invalid/attack sigs so we just fail them here rather than crash the thread.
return false;
}
}
/**
* Verifies the given ECDSA signature using the public key bytes against the message bytes,
* previously passed through a preprocessor function, which is normally a hashing function.
*
* @param data The data to verify.
* @param signature ASN.1 encoded signature.
* @param pub The public key bytes to use.
* @param preprocessor The function to apply to the data before verifying the signature, normally
* a hashing function.
* @return True if the verification is successful.
*/
@Override
public boolean verify(
final Bytes data,
final SECPSignature signature,
final SECPPublicKey pub,
final UnaryOperator<Bytes> preprocessor) {
checkArgument(preprocessor != null, "preprocessor must not be null");
return verify(preprocessor.apply(data), signature, pub);
}
/**
* Calculates an ECDH key agreement between the private and the public key.
*
* @param privKey The private key.
* @param theirPubKey The public key.
* @return The agreed secret.
*/
@Override
public Bytes32 calculateECDHKeyAgreement(
final SECPPrivateKey privKey, final SECPPublicKey theirPubKey) {
checkArgument(privKey != null, "missing private key");
checkArgument(theirPubKey != null, "missing remote public key");
final ECPrivateKeyParameters privKeyP = new ECPrivateKeyParameters(privKey.getD(), curve);
final ECPublicKeyParameters pubKeyP =
new ECPublicKeyParameters(theirPubKey.asEcPoint(curve), curve);
final ECDHBasicAgreement agreement = new ECDHBasicAgreement();
agreement.init(privKeyP);
final BigInteger agreed = agreement.calculateAgreement(pubKeyP);
return UInt256.valueOf(agreed).toBytes();
}
@Override
public SECPPrivateKey createPrivateKey(final BigInteger key) {
return SECPPrivateKey.create(key, ALGORITHM);
}
@Override
public SECPPrivateKey createPrivateKey(final Bytes32 key) {
return SECPPrivateKey.create(key, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final SECPPrivateKey privateKey) {
return SECPPublicKey.create(privateKey, curve, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final BigInteger key) {
return SECPPublicKey.create(key, ALGORITHM);
}
@Override
public SECPPublicKey createPublicKey(final Bytes encoded) {
return SECPPublicKey.create(encoded, ALGORITHM);
}
@Override
@ -369,47 +93,10 @@ public class SECP256K1 implements SignatureAlgorithm {
if (useNative) {
return recoverFromSignatureNative(dataHash, signature);
} else {
final BigInteger publicKeyBI =
recoverFromSignature(signature.getRecId(), signature.getR(), signature.getS(), dataHash);
return Optional.of(SECPPublicKey.create(publicKeyBI, ALGORITHM));
return super.recoverPublicKeyFromSignature(dataHash, signature);
}
}
@Override
public ECPoint publicKeyAsEcPoint(final SECPPublicKey publicKey) {
return publicKey.asEcPoint(curve);
}
@Override
public KeyPair createKeyPair(final SECPPrivateKey privateKey) {
return KeyPair.create(privateKey, curve, ALGORITHM);
}
@Override
public KeyPair generateKeyPair() {
return KeyPair.generate(keyPairGenerator, ALGORITHM);
}
@Override
public SECPSignature createSignature(final BigInteger r, final BigInteger s, final byte recId) {
return SECPSignature.create(r, s, recId, curveOrder);
}
@Override
public SECPSignature decodeSignature(final Bytes bytes) {
return SECPSignature.decode(bytes, curveOrder);
}
@Override
public BigInteger getHalfCurveOrder() {
return halfCurveOrder;
}
@Override
public String getProvider() {
return PROVIDER;
}
@Override
public String getCurveName() {
return CURVE_NAME;

@ -0,0 +1,39 @@
/*
* 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.crypto;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.math.ec.custom.sec.SecP256R1Curve;
public class SECP256R1 extends AbstractSECP256 {
private static final Logger LOG = LogManager.getLogger();
public static final String CURVE_NAME = "secp256r1";
public SECP256R1() {
super(CURVE_NAME, SecP256R1Curve.q);
}
@Override
public void enableNative() {
LOG.warn("Native secp256r1 requested but not available");
}
@Override
public String getCurveName() {
return CURVE_NAME;
}
}

@ -74,4 +74,6 @@ public interface SignatureAlgorithm {
SECPSignature createSignature(final BigInteger r, final BigInteger s, final byte recId);
SECPSignature decodeSignature(final Bytes bytes);
Bytes compressPublicKey(final SECPPublicKey uncompressedKey);
}

@ -15,8 +15,13 @@
package org.hyperledger.besu.crypto;
import com.google.common.annotations.VisibleForTesting;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class SignatureAlgorithmFactory {
private static final Logger LOG = LogManager.getLogger();
private static SignatureAlgorithm instance = null;
private SignatureAlgorithmFactory() {}
@ -33,6 +38,14 @@ public class SignatureAlgorithmFactory {
}
instance = signatureAlgorithmType.getInstance();
if (!SignatureAlgorithmType.isDefault(instance)) {
LOG.info(
new StringBuilder("The signature algorithm uses the elliptic curve ")
.append(instance.getCurveName())
.append(". The usage of alternative elliptic curves is still experimental.")
.toString());
}
}
/**

@ -18,13 +18,16 @@ import java.util.Iterator;
import java.util.Map;
import java.util.function.Supplier;
import com.google.common.collect.ImmutableMap;
public class SignatureAlgorithmType {
private static final Map<String, Supplier<SignatureAlgorithm>> SUPPORTED_ALGORITHMS =
Map.of("secp256k1", SECP256K1::new);
private static final String DEFAULT_EC_CURVE_NAME = "secp256k1";
private static final ImmutableMap<String, Supplier<SignatureAlgorithm>> SUPPORTED_ALGORITHMS =
ImmutableMap.of(DEFAULT_EC_CURVE_NAME, SECP256K1::new, "secp256r1", SECP256R1::new);
public static final Supplier<SignatureAlgorithm> DEFAULT_SIGNATURE_ALGORITHM_TYPE =
SUPPORTED_ALGORITHMS.get("secp256k1");
SUPPORTED_ALGORITHMS.get(DEFAULT_EC_CURVE_NAME);
private final Supplier<SignatureAlgorithm> instantiator;
@ -59,6 +62,10 @@ public class SignatureAlgorithmType {
return SUPPORTED_ALGORITHMS.containsKey(ecCurve);
}
public static boolean isDefault(final SignatureAlgorithm signatureAlgorithm) {
return signatureAlgorithm.getCurveName().equals(DEFAULT_EC_CURVE_NAME);
}
private static String getEcCurvesListAsString() {
Iterator<Map.Entry<String, Supplier<SignatureAlgorithm>>> it =
SUPPORTED_ALGORITHMS.entrySet().iterator();

@ -44,7 +44,6 @@ public class SECP256K1Test {
LocalDateTime.now(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
suiteName(SECP256K1Test.class);
;
}
@Before

@ -0,0 +1,126 @@
/*
* 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.crypto;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hyperledger.besu.crypto.Hash.keccak256;
import java.io.File;
import java.math.BigInteger;
import java.nio.file.Files;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class SECP256R1Test {
protected SECP256R1 secp256R1;
protected static String suiteStartTime = null;
protected static String suiteName = null;
@BeforeClass
public static void setTestSuiteStartTime() {
suiteStartTime =
LocalDateTime.now(ZoneId.systemDefault())
.format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"));
suiteName(SECP256R1Test.class);
}
@Before
public void setUp() {
secp256R1 = new SECP256R1();
}
public static void suiteName(final Class<?> clazz) {
suiteName = clazz.getSimpleName() + "-" + suiteStartTime;
}
public static String suiteName() {
return suiteName;
}
@Test
public void recoverPublicKeyFromSignature() {
final SECPPrivateKey privateKey =
secp256R1.createPrivateKey(
new BigInteger("c85ef7d79691fe79573b1a7064c19c1a9819ebdbd1faaab1a8ec92344438aaf4", 16));
final KeyPair keyPair = secp256R1.createKeyPair(privateKey);
final Bytes data = Bytes.wrap("This is an example of a signed message.".getBytes(UTF_8));
final Bytes32 dataHash = keccak256(data);
final SECPSignature signature = secp256R1.sign(dataHash, keyPair);
final SECPPublicKey recoveredPublicKey =
secp256R1.recoverPublicKeyFromSignature(dataHash, signature).get();
assertThat(recoveredPublicKey.toString()).isEqualTo(keyPair.getPublicKey().toString());
}
@Test
public void signatureGeneration() {
final SECPPrivateKey privateKey =
secp256R1.createPrivateKey(
new BigInteger("909753034398cf9371b88871c0a8b3051f1bb55d4f28d3d7261abe7d32adcdde", 16));
final KeyPair keyPair = secp256R1.createKeyPair(privateKey);
final Bytes data = Bytes.wrap("This is an example of a signed message.".getBytes(UTF_8));
final Bytes32 dataHash = keccak256(data);
final SECPSignature expectedSignature =
secp256R1.createSignature(
new BigInteger("6ae3ac096d1b69ab1e18a721689cc40f2710ab25c35a4f465b8384c470e7079b", 16),
new BigInteger("28a39d61a8812005312b552e022afd6fa3db323754f48033c87f4acf6e9960e6", 16),
(byte) 1);
final SECPSignature actualSignature = secp256R1.sign(dataHash, keyPair);
assertThat(actualSignature).isEqualTo(expectedSignature);
}
@Test
public void signatureVerification() {
final SECPPrivateKey privateKey =
secp256R1.createPrivateKey(
new BigInteger("a7e8b16ad7ffa26fce80be2b0e00008018aadf1b16dea4ecc913b8c1c4f18531", 16));
final KeyPair keyPair = secp256R1.createKeyPair(privateKey);
final Bytes data = Bytes.wrap("This is an example of a signed message.".getBytes(UTF_8));
final Bytes32 dataHash = keccak256(data);
final SECPSignature signature = secp256R1.sign(dataHash, keyPair);
assertThat(secp256R1.verify(data, signature, keyPair.getPublicKey(), Hash::keccak256)).isTrue();
}
@Test(expected = IllegalArgumentException.class)
public void invalidFileThrowsInvalidKeyPairException() throws Exception {
final File tempFile = Files.createTempFile(suiteName(), ".keypair").toFile();
tempFile.deleteOnExit();
Files.write(tempFile.toPath(), "not valid".getBytes(UTF_8));
KeyPairUtil.load(tempFile);
}
@Test(expected = IllegalArgumentException.class)
public void invalidMultiLineFileThrowsInvalidIdException() throws Exception {
final File tempFile = Files.createTempFile(suiteName(), ".keypair").toFile();
tempFile.deleteOnExit();
Files.write(tempFile.toPath(), "not\n\nvalid".getBytes(UTF_8));
KeyPairUtil.load(tempFile);
}
}

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

@ -21,6 +21,8 @@ import static org.apache.tuweni.bytes.Bytes.wrapBuffer;
import org.hyperledger.besu.crypto.Hash;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.SignatureAlgorithm;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.discovery.internal.Packet;
import org.hyperledger.besu.ethereum.p2p.discovery.internal.PeerDiscoveryController;
@ -53,6 +55,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.net.InetAddresses;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -62,7 +65,6 @@ import org.ethereum.beacon.discovery.schema.EnrField;
import org.ethereum.beacon.discovery.schema.IdentitySchema;
import org.ethereum.beacon.discovery.schema.NodeRecord;
import org.ethereum.beacon.discovery.schema.NodeRecordFactory;
import org.ethereum.beacon.discovery.util.Functions;
/**
* The peer discovery agent is the network component that sends and receives peer discovery messages
@ -71,6 +73,8 @@ import org.ethereum.beacon.discovery.util.Functions;
public abstract class PeerDiscoveryAgent {
private static final Logger LOG = LogManager.getLogger();
private static final String SEQ_NO_STORE_KEY = "local-enr-seqno";
private static final com.google.common.base.Supplier<SignatureAlgorithm> SIGNATURE_ALGORITHM =
Suppliers.memoize(SignatureAlgorithmFactory::getInstance);
// The devp2p specification says only accept packets up to 1280, but some
// clients ignore that, so we add in a little extra padding.
@ -212,7 +216,12 @@ public abstract class PeerDiscoveryAgent {
nodeRecordFactory.createFromValues(
sequenceNumber,
new EnrField(EnrField.ID, IdentitySchema.V4),
new EnrField(EnrField.PKEY_SECP256K1, Functions.compressPublicKey(id)),
new EnrField(
SIGNATURE_ALGORITHM.get().getCurveName(),
SIGNATURE_ALGORITHM
.get()
.compressPublicKey(
SIGNATURE_ALGORITHM.get().createPublicKey(id))),
new EnrField(EnrField.IP_V4, addressBytes),
new EnrField(EnrField.TCP, listeningPort),
new EnrField(EnrField.UDP, discoveryPort),

Loading…
Cancel
Save