Use native secp256k1 and altbn128 if present (#675)

Via JNA use native scep256k1 and altbn128 libraries. 

This is gated by two feature flags and is disabled by default.

* `--Xsecp256k1-native-enabled` enables native secp256k1 across all of besu
* `--Xaltbn128-native-enabled` enables native altbn128 in the precompiled contracts

Signed-off-by: Danno Ferrin <danno.ferrin@gmail.com>
pull/768/head
Danno Ferrin 5 years ago committed by GitHub
parent 0babfef244
commit 10d2c87647
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
  2. 4
      besu/src/test/resources/everything_config.toml
  3. 1
      build.gradle
  4. 2
      crypto/build.gradle
  5. 170
      crypto/src/main/java/org/hyperledger/besu/crypto/SECP256K1.java
  6. 2
      ethereum/core/build.gradle
  7. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AltBN128AddPrecompiledContract.java
  8. 21
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AltBN128MulPrecompiledContract.java
  9. 33
      ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/precompiles/AltBN128PairingPrecompiledContract.java
  10. 6
      gradle/check-licenses.gradle
  11. 7
      gradle/versions.gradle

@ -67,6 +67,7 @@ import org.hyperledger.besu.config.experimental.ExperimentalEIPs;
import org.hyperledger.besu.controller.BesuController;
import org.hyperledger.besu.controller.BesuControllerBuilder;
import org.hyperledger.besu.crypto.KeyPairUtil;
import org.hyperledger.besu.crypto.SECP256K1;
import org.hyperledger.besu.enclave.EnclaveFactory;
import org.hyperledger.besu.ethereum.api.graphql.GraphQLConfiguration;
import org.hyperledger.besu.ethereum.api.jsonrpc.JsonRpcConfiguration;
@ -84,6 +85,7 @@ import org.hyperledger.besu.ethereum.core.Wei;
import org.hyperledger.besu.ethereum.eth.sync.SyncMode;
import org.hyperledger.besu.ethereum.eth.sync.SynchronizerConfiguration;
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
import org.hyperledger.besu.ethereum.mainnet.precompiles.AltBN128PairingPrecompiledContract;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURL;
import org.hyperledger.besu.ethereum.p2p.peers.StaticNodesParser;
@ -854,6 +856,18 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
description = "Path to PID file (optional)")
private final Path pidPath = null;
@CommandLine.Option(
names = {"--Xsecp256k1-native-enabled"},
description = "Path to PID file (optional)",
arity = "1")
private final Boolean nativeSecp256k1 = Boolean.FALSE;
@CommandLine.Option(
names = {"--Xaltbn128-native-enabled"},
description = "Path to PID file (optional)",
arity = "1")
private final Boolean nativeAltbn128 = Boolean.FALSE;
private EthNetworkConfig ethNetworkConfig;
private JsonRpcConfiguration jsonRpcConfiguration;
private GraphQLConfiguration graphQLConfiguration;
@ -932,6 +946,7 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
public void run() {
try {
configureLogging(true);
configureNativeLibs();
logger.info("Starting Besu version: {}", BesuInfo.nodeName(identityString));
// Need to create vertx after cmdline has been parsed, such that metricSystem is configurable
vertx = createVertx(createVertxOptions(metricsSystem.get()));
@ -1102,6 +1117,15 @@ public class BesuCommand implements DefaultCommandValues, Runnable {
}
}
private void configureNativeLibs() {
if (nativeAltbn128) {
AltBN128PairingPrecompiledContract.enableNative();
}
if (nativeSecp256k1) {
SECP256K1.enableNative();
}
}
private BesuCommand validateOptions() {
issueOptionWarnings();

@ -144,3 +144,7 @@ target-gas-limit=8000000
# transaction log bloom filter caching
auto-log-bloom-caching-enabled=true
# feature flags
Xsecp256k1-native-enabled=false
Xaltbn128-native-enabled=false

@ -123,6 +123,7 @@ allprojects {
jcenter()
mavenCentral()
mavenLocal()
maven {url "https://hyperledger-org.bintray.com/besu-repo"}
maven { url "https://consensys.bintray.com/pegasys-repo" }
maven { url "https://repo.spring.io/libs-release" }
}

@ -34,9 +34,11 @@ dependencies {
api 'org.bouncycastle:bcprov-jdk15on'
implementation 'com.google.guava:guava'
implementation 'net.java.dev.jna:jna'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.apache.tuweni:tuweni-units'
implementation 'org.hyperledger.besu:secp256k1'
runtimeOnly 'org.apache.logging.log4j:log4j-core'

@ -16,8 +16,15 @@ package org.hyperledger.besu.crypto;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.SECP256K1_EC_UNCOMPRESSED;
import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1;
import org.hyperledger.besu.nativelib.secp256k1.LibSecp256k1.secp256k1_ecdsa_recoverable_signature;
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;
@ -25,8 +32,14 @@ import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import com.google.common.base.Suppliers;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.LongByReference;
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.bytes.MutableBytes;
@ -59,6 +72,10 @@ import org.bouncycastle.math.ec.custom.sec.SecP256K1Curve;
*/
public class SECP256K1 {
private static final Logger LOG = LogManager.getLogger();
private static boolean useNative = false;
public static final String ALGORITHM = "ECDSA";
public static final String CURVE_NAME = "secp256k1";
public static final String PROVIDER = "BC";
@ -89,6 +106,38 @@ public class SECP256K1 {
}
}
public static void enableNative() {
useNative = LibSecp256k1.CONTEXT != null;
LOG.info(useNative ? "Using native secp256k1" : "Native secp256k1 requested but not available");
}
public static Signature sign(final Bytes32 dataHash, final KeyPair keyPair) {
if (useNative) {
return signNative(dataHash, keyPair);
} else {
return signDefault(dataHash, keyPair);
}
}
/**
* 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.
*/
public static boolean verify(final Bytes data, final Signature signature, final PublicKey pub) {
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 static ECPoint decompressKey(final BigInteger xBN, final boolean yBit) {
final X9IntegerConverter x9 = new X9IntegerConverter();
@ -178,7 +227,7 @@ public class SECP256K1 {
return new BigInteger(1, Arrays.copyOfRange(qBytes, 1, qBytes.length));
}
public static Signature sign(final Bytes32 dataHash, final KeyPair keyPair) {
private static Signature signDefault(final Bytes32 dataHash, final KeyPair keyPair) {
final ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
final ECPrivateKeyParameters privKey =
@ -232,18 +281,8 @@ public class SECP256K1 {
return new Signature(nativeR, s, (byte) recId);
}
/**
* 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.
*/
public static boolean verify(final Bytes data, final Signature signature, final PublicKey pub) {
private static boolean verifyDefault(
final Bytes data, final Signature signature, final PublicKey pub) {
final ECDSASigner signer = new ECDSASigner();
final Bytes toDecode = Bytes.wrap(Bytes.of((byte) 4), pub.getEncodedBytes());
final ECPublicKeyParameters params =
@ -301,6 +340,95 @@ public class SECP256K1 {
return UInt256.valueOf(agreed).toBytes();
}
private static Signature signNative(final Bytes32 dataHash, final KeyPair keyPair) {
final LibSecp256k1.secp256k1_ecdsa_recoverable_signature signature =
new secp256k1_ecdsa_recoverable_signature();
// sign in internal form
if (LibSecp256k1.secp256k1_ecdsa_sign_recoverable(
LibSecp256k1.CONTEXT,
signature,
dataHash.toArrayUnsafe(),
keyPair.privateKey.getEncoded(),
null,
null)
== 0) {
throw new RuntimeException(
"Could not natively sign. Private Key is invalid or default nonce generation failed.");
}
// encode to compact form
final ByteBuffer compactSig = ByteBuffer.allocate(64);
final IntByReference recId = new IntByReference(0);
LibSecp256k1.secp256k1_ecdsa_recoverable_signature_serialize_compact(
LibSecp256k1.CONTEXT, compactSig, recId, signature);
compactSig.flip();
final byte[] sig = compactSig.array();
// wrap in signature object
final Bytes32 r = Bytes32.wrap(sig, 0);
final Bytes32 s = Bytes32.wrap(sig, 32);
return Signature.create(
r.toUnsignedBigInteger(), s.toUnsignedBigInteger(), (byte) recId.getValue());
}
private static boolean verifyNative(
final Bytes data, final Signature signature, final PublicKey pub) {
// translate signature
final LibSecp256k1.secp256k1_ecdsa_signature _signature = new secp256k1_ecdsa_signature();
if (LibSecp256k1.secp256k1_ecdsa_signature_parse_compact(
LibSecp256k1.CONTEXT, _signature, signature.encodedBytes().toArrayUnsafe())
== 0) {
throw new IllegalArgumentException("Could not parse signature");
}
// translate key
final LibSecp256k1.secp256k1_pubkey _pub = new secp256k1_pubkey();
final Bytes encodedPubKey = Bytes.concatenate(Bytes.of(0x04), pub.getEncodedBytes());
if (LibSecp256k1.secp256k1_ec_pubkey_parse(
LibSecp256k1.CONTEXT, _pub, encodedPubKey.toArrayUnsafe(), encodedPubKey.size())
== 0) {
throw new IllegalArgumentException("Could not parse public key");
}
return LibSecp256k1.secp256k1_ecdsa_verify(
LibSecp256k1.CONTEXT, _signature, data.toArrayUnsafe(), _pub)
!= 0;
}
private static Optional<PublicKey> recoverFromSignatureNative(
final Bytes32 dataHash, final Signature signature) {
// parse the sig
final LibSecp256k1.secp256k1_ecdsa_recoverable_signature parsedSignature =
new LibSecp256k1.secp256k1_ecdsa_recoverable_signature();
final Bytes encodedSig = signature.encodedBytes();
if (LibSecp256k1.secp256k1_ecdsa_recoverable_signature_parse_compact(
LibSecp256k1.CONTEXT,
parsedSignature,
encodedSig.slice(0, 64).toArrayUnsafe(),
encodedSig.get(64))
== 0) {
throw new IllegalArgumentException("Could not parse signature");
}
// recover the key
final LibSecp256k1.secp256k1_pubkey newPubKey = new LibSecp256k1.secp256k1_pubkey();
if (LibSecp256k1.secp256k1_ecdsa_recover(
LibSecp256k1.CONTEXT, newPubKey, parsedSignature, dataHash.toArrayUnsafe())
== 0) {
throw new IllegalArgumentException("Could not recover public key");
}
// parse the key
final ByteBuffer recoveredKey = ByteBuffer.allocate(65);
final LongByReference keySize = new LongByReference(recoveredKey.limit());
LibSecp256k1.secp256k1_ec_pubkey_serialize(
LibSecp256k1.CONTEXT, recoveredKey, keySize, newPubKey, SECP256K1_EC_UNCOMPRESSED);
return Optional.of(PublicKey.create(Bytes.wrapByteBuffer(recoveredKey).slice(1)));
}
public static class PrivateKey implements java.security.PrivateKey {
private final Bytes32 encoded;
@ -319,10 +447,6 @@ public class SECP256K1 {
return new PrivateKey(key);
}
public ECPoint asEcPoint() {
return CURVE.getCurve().decodePoint(encoded.toArrayUnsafe());
}
@Override
public boolean equals(final Object other) {
if (!(other instanceof PrivateKey)) {
@ -411,11 +535,15 @@ public class SECP256K1 {
public static Optional<PublicKey> recoverFromSignature(
final Bytes32 dataHash, final Signature signature) {
if (useNative) {
return recoverFromSignatureNative(dataHash, signature);
} else {
final BigInteger publicKeyBI =
SECP256K1.recoverFromSignature(
signature.getRecId(), signature.getR(), signature.getS(), dataHash);
return Optional.ofNullable(publicKeyBI).map(PublicKey::create);
}
}
private PublicKey(final Bytes encoded) {
checkNotNull(encoded);
@ -552,7 +680,9 @@ public class SECP256K1 {
private final BigInteger r;
private final BigInteger s;
private Signature(final BigInteger r, final BigInteger s, final byte recId) {
private final Supplier<Bytes> encoded = Suppliers.memoize(this::_encodedBytes);
Signature(final BigInteger r, final BigInteger s, final byte recId) {
this.r = r;
this.s = s;
this.recId = recId;
@ -604,6 +734,10 @@ public class SECP256K1 {
}
public Bytes encodedBytes() {
return encoded.get();
}
private Bytes _encodedBytes() {
final MutableBytes bytes = MutableBytes.create(BYTES_REQUIRED);
UInt256.valueOf(r).toBytes().copyTo(bytes, 0);
UInt256.valueOf(s).toBytes().copyTo(bytes, 32);

@ -42,9 +42,11 @@ dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'com.google.guava:guava'
implementation 'io.vertx:vertx-core'
implementation 'net.java.dev.jna:jna'
implementation 'org.apache.logging.log4j:log4j-api'
implementation 'org.apache.tuweni:tuweni-bytes'
implementation 'org.apache.tuweni:tuweni-units'
implementation 'org.hyperledger.besu:altbn128'
runtimeOnly 'org.apache.logging.log4j:log4j-core'

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.mainnet.precompiles;
import static org.hyperledger.besu.nativelib.altbn128.LibAltbn128.altbn128_add_precompiled;
import org.hyperledger.besu.crypto.altbn128.AltBn128Point;
import org.hyperledger.besu.crypto.altbn128.Fq;
import org.hyperledger.besu.ethereum.core.Gas;
@ -24,6 +26,7 @@ import org.hyperledger.besu.ethereum.vm.MessageFrame;
import java.math.BigInteger;
import java.util.Arrays;
import com.sun.jna.ptr.IntByReference;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;
@ -51,6 +54,14 @@ public class AltBN128AddPrecompiledContract extends AbstractPrecompiledContract
@Override
public Bytes compute(final Bytes input, final MessageFrame messageFrame) {
if (AltBN128PairingPrecompiledContract.useNative) {
return computeNative(input);
} else {
return computeDefault(input);
}
}
private static Bytes computeDefault(final Bytes input) {
final BigInteger x1 = extractParameter(input, 0, 32);
final BigInteger y1 = extractParameter(input, 32, 32);
final BigInteger x2 = extractParameter(input, 64, 32);
@ -71,6 +82,16 @@ public class AltBN128AddPrecompiledContract extends AbstractPrecompiledContract
return result;
}
private static Bytes computeNative(final Bytes input) {
final byte[] output = new byte[64];
final IntByReference outputSize = new IntByReference(64);
if (altbn128_add_precompiled(input.toArrayUnsafe(), input.size(), output, outputSize) == 0) {
return Bytes.wrap(output, 0, outputSize.getValue());
} else {
return null;
}
}
private static BigInteger extractParameter(
final Bytes input, final int offset, final int length) {
if (offset > input.size() || length == 0) {

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.mainnet.precompiles;
import static org.hyperledger.besu.nativelib.altbn128.LibAltbn128.altbn128_mul_precompiled;
import org.hyperledger.besu.crypto.altbn128.AltBn128Point;
import org.hyperledger.besu.crypto.altbn128.Fq;
import org.hyperledger.besu.ethereum.core.Gas;
@ -24,6 +26,7 @@ import org.hyperledger.besu.ethereum.vm.MessageFrame;
import java.math.BigInteger;
import java.util.Arrays;
import com.sun.jna.ptr.IntByReference;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.MutableBytes;
@ -55,6 +58,14 @@ public class AltBN128MulPrecompiledContract extends AbstractPrecompiledContract
@Override
public Bytes compute(final Bytes input, final MessageFrame messageFrame) {
if (AltBN128PairingPrecompiledContract.useNative) {
return computeNative(input);
} else {
return computeDefault(input);
}
}
private static Bytes computeDefault(final Bytes input) {
final BigInteger x = extractParameter(input, 0, 32);
final BigInteger y = extractParameter(input, 32, 32);
final BigInteger n = extractParameter(input, 64, 32);
@ -74,6 +85,16 @@ public class AltBN128MulPrecompiledContract extends AbstractPrecompiledContract
return result;
}
private static Bytes computeNative(final Bytes input) {
final byte[] output = new byte[64];
final IntByReference outputSize = new IntByReference(64);
if (altbn128_mul_precompiled(input.toArrayUnsafe(), input.size(), output, outputSize) == 0) {
return Bytes.wrap(output, 0, outputSize.getValue());
} else {
return null;
}
}
private static BigInteger extractParameter(
final Bytes input, final int offset, final int length) {
if (offset > input.size() || length == 0) {

@ -14,6 +14,8 @@
*/
package org.hyperledger.besu.ethereum.mainnet.precompiles;
import static org.hyperledger.besu.nativelib.altbn128.LibAltbn128.altbn128_pairing_precompiled;
import org.hyperledger.besu.crypto.altbn128.AltBn128Fq12Pairer;
import org.hyperledger.besu.crypto.altbn128.AltBn128Fq2Point;
import org.hyperledger.besu.crypto.altbn128.AltBn128Point;
@ -24,16 +26,29 @@ import org.hyperledger.besu.ethereum.core.Gas;
import org.hyperledger.besu.ethereum.mainnet.AbstractPrecompiledContract;
import org.hyperledger.besu.ethereum.vm.GasCalculator;
import org.hyperledger.besu.ethereum.vm.MessageFrame;
import org.hyperledger.besu.nativelib.altbn128.LibAltbn128;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.ptr.IntByReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public class AltBN128PairingPrecompiledContract extends AbstractPrecompiledContract {
static boolean useNative = false;
private static final Logger LOG = LogManager.getLogger();
public static void enableNative() {
useNative = LibAltbn128.ENABLED;
LOG.info(useNative ? "Using native alt bn128" : "Native alt bn128 requested but not available");
}
private static final int FIELD_LENGTH = 32;
private static final int PARAMETER_LENGTH = 192;
@ -74,7 +89,14 @@ public class AltBN128PairingPrecompiledContract extends AbstractPrecompiledContr
if (input.size() % PARAMETER_LENGTH != 0) {
return null;
}
if (AltBN128PairingPrecompiledContract.useNative) {
return computeNative(input);
} else {
return computeDefault(input);
}
}
private static Bytes computeDefault(final Bytes input) {
final int parameters = input.size() / PARAMETER_LENGTH;
final List<AltBn128Point> a = new ArrayList<>();
final List<AltBn128Fq2Point> b = new ArrayList<>();
@ -112,6 +134,17 @@ public class AltBN128PairingPrecompiledContract extends AbstractPrecompiledContr
}
}
private static Bytes computeNative(final Bytes input) {
final byte[] output = new byte[32];
final IntByReference outputSize = new IntByReference(32);
if (altbn128_pairing_precompiled(input.toArrayUnsafe(), input.size(), output, outputSize)
== 0) {
return Bytes.wrap(output, 0, outputSize.getValue());
} else {
return null;
}
}
private static BigInteger extractParameter(
final Bytes input, final int offset, final int length) {
if (offset > input.size() || length == 0) {

@ -110,7 +110,11 @@ downloadLicenses {
(group('besu.plugins')) : apache,
(group('besu.services')) : apache,
// RocksDB is dual licensed under Apache v2.0 and GPL 2 licenses
// JNA is dual licensed under Apache v2.0 or LGPL 2 licenses
// Explicitly declare that we are using the Apache v2.0 license
(group('net.java.dev.jna')) : apache,
// RocksDB is dual licensed under Apache v2.0 or GPL 2 licenses
// Explicitly declare that we are using the Apache v2.0 license
(group('org.rocksdb')) : apache,

@ -59,7 +59,9 @@ dependencyManagement {
dependency 'junit:junit:4.13'
dependency 'net.consensys:orion:1.5.0-SNAPSHOT'
dependency 'net.consensys:orion:1.5.0'
dependency 'net.java.dev.jna:jna:5.5.0'
dependency 'org.apache.commons:commons-compress:1.20'
dependency 'org.apache.commons:commons-text:1.8'
@ -83,6 +85,9 @@ dependencyManagement {
dependency 'org.bouncycastle:bcpkix-jdk15on:1.64'
dependency 'org.bouncycastle:bcprov-jdk15on:1.64'
dependency 'org.hyperledger.besu:altbn128:0.1.0'
dependency 'org.hyperledger.besu:secp256k1:0.1.0'
dependency 'org.java-websocket:Java-WebSocket:1.4.0'
dependency 'org.jupnp:org.jupnp.support:2.5.2'

Loading…
Cancel
Save