-
Notifications
You must be signed in to change notification settings - Fork 0
feat(vm): add optimized BN128 precompiled contracts via IPC #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,8 @@ | |
| import org.tron.common.crypto.Rsv; | ||
| import org.tron.common.crypto.SignUtils; | ||
| import org.tron.common.crypto.SignatureInterface; | ||
| import org.tron.common.crypto.bn128.BN128Service; | ||
| import org.tron.common.crypto.bn128.SocketBN128Client; | ||
| import org.tron.common.crypto.zksnark.BN128; | ||
| import org.tron.common.crypto.zksnark.BN128Fp; | ||
| import org.tron.common.crypto.zksnark.BN128G1; | ||
|
|
@@ -426,6 +428,39 @@ public abstract static class PrecompiledContract { | |
| @Getter | ||
| @Setter | ||
| private long vmShouldEndInUs; | ||
| protected static BN128Service client; | ||
| // BN128 client connection pool size | ||
| private static final int BN128_CLIENT_POOL_SIZE = 8; | ||
|
|
||
| static { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P0: The Consider lazy-initializing the client on first use (double-checked locking or an explicit Prompt for AI agents |
||
| if (VMConfig.allowOptimizedBn128()) { | ||
| try { | ||
| int bn128Port = VMConfig.getBN128ClientPort(); | ||
| int bn128Timeout = VMConfig.getBN128SocketTimeout(); | ||
| client = new SocketBN128Client(BN128_CLIENT_POOL_SIZE, bn128Port, bn128Timeout); | ||
| logger.info("SocketBn128Client initialized with pool size={}, port={}, timeout={}ms", | ||
| BN128_CLIENT_POOL_SIZE, bn128Port, bn128Timeout); | ||
|
|
||
| // Register shutdown hook for graceful cleanup | ||
| Runtime.getRuntime().addShutdownHook(new Thread(() -> { | ||
| try { | ||
| if (client != null) { | ||
| logger.info("Closing SocketBn128Client..."); | ||
| client.close(); | ||
| logger.info("SocketBn128Client closed successfully"); | ||
| } | ||
| } catch (Exception e) { | ||
| logger.error("Error occurred while closing SocketBn128Client", e); | ||
| } | ||
| })); | ||
| } catch (Exception e) { | ||
| logger.warn( | ||
| "Failed to initialize SocketBn128Client, falling back to non-optimized mode: {}", | ||
| e.getMessage()); | ||
| client = null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public abstract long getEnergyForData(byte[] data); | ||
|
|
||
|
|
@@ -757,6 +792,18 @@ public Pair<Boolean, byte[]> execute(byte[] data) { | |
| data = EMPTY_BYTE_ARRAY; | ||
| } | ||
|
|
||
| if (VMConfig.allowOptimizedBn128() && client != null) { | ||
| byte[] input = data.length > 128 ? Arrays.copyOfRange(data, 0, 128) : data; | ||
| try { | ||
| return client.bn128Add(input); | ||
| } catch (Exception e) { | ||
| logger.warn( | ||
| "Optimized BN128 add failed for input length {}, falling back to non-optimized " | ||
| + "implementation: {}", | ||
| data.length, e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| byte[] x1 = parseWord(data, 0); | ||
| byte[] y1 = parseWord(data, 1); | ||
|
|
||
|
|
@@ -811,6 +858,18 @@ public Pair<Boolean, byte[]> execute(byte[] data) { | |
| data = EMPTY_BYTE_ARRAY; | ||
| } | ||
|
|
||
| if (VMConfig.allowOptimizedBn128() && client != null) { | ||
| byte[] input = data.length > 96 ? Arrays.copyOfRange(data, 0, 96) : data; | ||
| try { | ||
| return client.bn128Mul(input); | ||
| } catch (Exception e) { | ||
| logger.warn( | ||
| "Optimized BN128 multiplication failed for input length {}, falling back to " | ||
| + "non-optimized implementation: {}", | ||
| data.length, e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| byte[] x = parseWord(data, 0); | ||
| byte[] y = parseWord(data, 1); | ||
|
|
||
|
|
@@ -844,7 +903,8 @@ public Pair<Boolean, byte[]> execute(byte[] data) { | |
| public static class BN128Pairing extends PrecompiledContract { | ||
|
|
||
| private static final int PAIR_SIZE = 192; | ||
|
|
||
| // Limit to 100 pairs (19,200 bytes) in optimized mode to guarantee security | ||
| private static final int MAX_PAIR_SIZE_LIMIT = 192 * 100; | ||
| @Override | ||
| public long getEnergyForData(byte[] data) { | ||
| if (VMConfig.allowTvmIstanbul()) { | ||
|
|
@@ -875,6 +935,17 @@ public Pair<Boolean, byte[]> execute(byte[] data) { | |
| return Pair.of(false, EMPTY_BYTE_ARRAY); | ||
| } | ||
|
|
||
| if (VMConfig.allowOptimizedBn128() && client != null && data.length <= MAX_PAIR_SIZE_LIMIT) { | ||
| try { | ||
| return client.bn128Pairing(data); | ||
| } catch (Exception e) { | ||
| logger.warn( | ||
| "Optimized BN128 pairing failed for input length {}, falling back to " | ||
| + "non-optimized implementation: {}", | ||
| data.length, e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| PairingCheck check = PairingCheck.create(); | ||
|
|
||
| // iterating over all pairs | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -238,6 +238,9 @@ public class DynamicPropertiesStore extends TronStoreWithRevoking<BytesCapsule> | |
| private static final byte[] ALLOW_TVM_SELFDESTRUCT_RESTRICTION = | ||
| "ALLOW_TVM_SELFDESTRUCT_RESTRICTION".getBytes(); | ||
|
|
||
| private static final byte[] ALLOW_OPTIMIZED_BN128 = | ||
| "ALLOW_OPTIMIZED_BN128".getBytes(); | ||
|
|
||
| @Autowired | ||
| private DynamicPropertiesStore(@Value("properties") String dbName) { | ||
| super(dbName); | ||
|
|
@@ -2967,7 +2970,32 @@ public void saveAllowTvmSelfdestructRestriction(long value) { | |
| public boolean allowTvmSelfdestructRestriction() { | ||
| return getAllowTvmSelfdestructRestriction() == 1L; | ||
| } | ||
|
|
||
|
|
||
| public void saveAllowOptimizedBn128(long allowOptimizedBn128) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Naming inconsistency: save method uses Prompt for AI agents |
||
| this.put(ALLOW_OPTIMIZED_BN128, | ||
| new BytesCapsule(ByteArray.fromLong(allowOptimizedBn128))); | ||
| } | ||
|
|
||
| public long getAllowOptimizedBN128() { | ||
| return Optional.ofNullable(getUnchecked(ALLOW_OPTIMIZED_BN128)) | ||
| .map(BytesCapsule::getData) | ||
| .map(ByteArray::toLong) | ||
| .orElse(CommonParameter.getInstance() | ||
| .getAllowOptimizedBN128()); | ||
| } | ||
|
Comment on lines
+2974
to
+2985
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bootstrap
Suggested constructor fix try {
this.getAllowDynamicEnergy();
} catch (IllegalArgumentException e) {
this.saveAllowDynamicEnergy(CommonParameter.getInstance().getAllowDynamicEnergy());
}
+
+ if (getUnchecked(ALLOW_OPTIMIZED_BN128) == null) {
+ this.saveAllowOptimizedBn128(CommonParameter.getInstance().getAllowOptimizedBN128());
+ }
try {
this.getDynamicEnergyThreshold();
} catch (IllegalArgumentException e) {
this.saveDynamicEnergyThreshold(CommonParameter.getInstance().getDynamicEnergyThreshold());🤖 Prompt for AI Agents |
||
|
|
||
| public boolean allowOptimizedBN128() { | ||
| return getAllowOptimizedBN128() == 1L; | ||
| } | ||
|
|
||
| public int getBN128Port() { | ||
| return CommonParameter.getInstance().getBn128ServerPort(); | ||
| } | ||
|
|
||
| public int getBN128SocketTimeout() { | ||
| return CommonParameter.getInstance().getBn128SocketTimeout(); | ||
| } | ||
|
|
||
| public void saveProposalExpireTime(long proposalExpireTime) { | ||
| this.put(PROPOSAL_EXPIRE_TIME, new BytesCapsule(ByteArray.fromLong(proposalExpireTime))); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -632,6 +632,18 @@ public class CommonParameter { | |||||
| @Getter | ||||||
| @Setter | ||||||
| public long allowTvmBlob; | ||||||
| @Getter | ||||||
| @Setter | ||||||
| public long allowOptimizedBN128; | ||||||
| @Getter | ||||||
| @Setter | ||||||
| public boolean launchBN128Server = false; | ||||||
| @Getter | ||||||
| @Setter | ||||||
| public int bn128ServerPort = 9001; | ||||||
| @Getter | ||||||
| @Setter | ||||||
| public int bn128SocketTimeout = 30; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Default Prompt for AI agents
Suggested change
|
||||||
|
|
||||||
| private static double calcMaxTimeRatio() { | ||||||
| return 5.0; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,8 @@ repositories { | |||||||||
|
|
||||||||||
| dependencies { | ||||||||||
| api project(":common") | ||||||||||
| api 'net.java.dev.jna:jna:5.12.1' | ||||||||||
| api 'com.github.federico2014.besu-native:gnark:1.3.11' | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P0: Supply chain risk: This dependency pulls native cryptographic binaries from a personal GitHub fork ( Prompt for AI agents
Comment on lines
+15
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify that java-library is applied somewhere in Gradle config
rg -n "id ['\"]java-library['\"]|apply plugin:\\s*['\"]java-library['\"]" --glob "**/*.gradle*"
# Quick scan for potential public API leakage of JNA/gnark types
rg -n --type=java "^\\s*public\\s+.*(com\\.sun\\.jna|gnark|besu-native)"Repository: Federico2014/java-tron Length of output: 113 🏁 Script executed: # First, let's read the crypto/build.gradle file to see the context
cat -n crypto/build.gradle | head -30Repository: Federico2014/java-tron Length of output: 842 🏁 Script executed: # Check if crypto/build.gradle applies java-library plugin
rg -n "apply plugin:|id '" crypto/build.gradle | head -20Repository: Federico2014/java-tron Length of output: 81 🏁 Script executed: # Search for public usage of JNA and gnark types in the crypto module
rg -n "public.*jna|public.*gnark|public.*JNA|public.*Gnark" --type=java crypto/Repository: Federico2014/java-tron Length of output: 48 🏁 Script executed: # Check if the crypto module exports JNA/gnark types in any public interfaces
rg -n "import.*com\.sun\.jna|import.*gnark|import.*besu-native" --type=java crypto/ | head -20Repository: Federico2014/java-tron Length of output: 306 🏁 Script executed: # Check if BN128Executor is a public class and what it exposes
cat -n crypto/src/main/java/org/tron/common/crypto/bn128/BN128Executor.java | head -50Repository: Federico2014/java-tron Length of output: 1922 🏁 Script executed: # Check all public methods/fields in crypto module for JNA/gnark type exposure
rg -n "public\s+(class|interface|record|enum|.*\s+(jna|gnark|JNA|Gnark|IntByReference|LibGnark))" --type=java crypto/src/main/java/ -A 3 | head -40Repository: Federico2014/java-tron Length of output: 3198 Switch to The crypto module uses the plain Suggested change- api 'net.java.dev.jna:jna:5.12.1'
- api 'com.github.federico2014.besu-native:gnark:1.3.11'
+ implementation 'net.java.dev.jna:jna:5.12.1'
+ implementation 'com.github.federico2014.besu-native:gnark:1.3.11'📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Supply-chain risk: this pulls a security-critical cryptographic library (BN128 precompiled contracts) from a personal GitHub fork via JitPack rather than from the official Hyperledger Besu artifact. JitPack builds from the fork's source on the fly, so the fork owner can push arbitrary changes that will silently flow into this project. For a library that implements elliptic-curve operations used in consensus, this is a high-risk dependency. Consider using the official artifact from Maven Central (e.g., Prompt for AI agents |
||||||||||
| } | ||||||||||
|
|
||||||||||
| jacocoTestReport { | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package org.tron.common.crypto.bn128; | ||
|
|
||
| import com.sun.jna.ptr.IntByReference; | ||
| import java.util.Arrays; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.apache.commons.lang3.tuple.Pair; | ||
| import org.hyperledger.besu.nativelib.gnark.LibGnarkEIP196; | ||
|
|
||
| @Slf4j(topic = "crypto") | ||
| public class BN128Executor { | ||
|
|
||
| public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; | ||
|
|
||
| private static final int PAIR_SIZE = 192; | ||
|
|
||
| // Limit to 100 pairs (19,200 bytes) in optimized mode to prevent timeout attacks | ||
| private static final int MAX_PAIR_SIZE_LIMIT = 192 * 100; | ||
|
|
||
| public static Pair<Boolean, byte[]> add(byte[] data) { | ||
| if (data == null) { | ||
| data = EMPTY_BYTE_ARRAY; | ||
| } | ||
|
|
||
| byte[] input = data.length > 128 | ||
| ? Arrays.copyOfRange(data, 0, 128) : data; | ||
| Pair<Boolean, byte[]> result = executeEIP196Operation( | ||
| LibGnarkEIP196.EIP196_ADD_OPERATION_RAW_VALUE, input); | ||
| return result; | ||
| } | ||
|
|
||
| public static Pair<Boolean, byte[]> mul(byte[] data) { | ||
| if (data == null) { | ||
| data = EMPTY_BYTE_ARRAY; | ||
| } | ||
|
|
||
| byte[] input = data.length > 96 | ||
| ? Arrays.copyOfRange(data, 0, 96) : data; | ||
| Pair<Boolean, byte[]> result = executeEIP196Operation( | ||
| LibGnarkEIP196.EIP196_MUL_OPERATION_RAW_VALUE, input); | ||
| return result; | ||
| } | ||
|
|
||
| public static Pair<Boolean, byte[]> pairing(byte[] data) { | ||
| if (data == null) { | ||
| data = EMPTY_BYTE_ARRAY; | ||
| } | ||
|
|
||
| if (data.length > MAX_PAIR_SIZE_LIMIT | ||
| || data.length % PAIR_SIZE > 0) { | ||
| return Pair.of(false, EMPTY_BYTE_ARRAY); | ||
| } | ||
| Pair<Boolean, byte[]> result = executeEIP196Operation( | ||
| LibGnarkEIP196.EIP196_PAIR_OPERATION_RAW_VALUE, data); | ||
| return result; | ||
|
|
||
| } | ||
|
|
||
|
|
||
| private static Pair<Boolean, byte[]> executeEIP196Operation( | ||
| byte operation, byte[] data) { | ||
| if (!LibGnarkEIP196.ENABLED) { | ||
| logger.warn("Native BN128 library not available, " | ||
| + "cannot execute optimized path"); | ||
| return null; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Returning Prompt for AI agentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Prompt for AI agents |
||
| } | ||
|
|
||
| final byte[] output = | ||
| new byte[LibGnarkEIP196.EIP196_PREALLOCATE_FOR_RESULT_BYTES]; | ||
| final IntByReference outputLength = new IntByReference(); | ||
| final byte[] error = | ||
| new byte[LibGnarkEIP196.EIP196_PREALLOCATE_FOR_ERROR_BYTES]; | ||
| final IntByReference errorLength = new IntByReference(); | ||
|
|
||
| int ret = LibGnarkEIP196.eip196_perform_operation( | ||
| operation, data, data.length, | ||
| output, outputLength, error, errorLength); | ||
|
|
||
| if (ret == 0) { | ||
| return Pair.of(true, | ||
| subArray(output, 0, outputLength.getValue())); | ||
| } else { | ||
| return Pair.of(false, EMPTY_BYTE_ARRAY); | ||
| } | ||
| } | ||
|
|
||
| public static byte[] subArray(byte[] input, int start, int end) { | ||
| byte[] result = new byte[end - start]; | ||
| System.arraycopy(input, start, result, 0, end - start); | ||
| return result; | ||
| } | ||
|
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: BN128 client field, static initializer (with socket I/O), and shutdown hook are placed on the abstract
PrecompiledContractbase class, which is the parent of all precompiled contracts. Only the three BN128 subclasses useclient. Consider moving this to a dedicated BN128 helper/holder class to avoid coupling all precompiled contracts to BN128 socket infrastructure.Prompt for AI agents