diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java index 62cc82873..14d2a5db8 100644 --- a/core/java/src/net/i2p/util/NativeBigInteger.java +++ b/core/java/src/net/i2p/util/NativeBigInteger.java @@ -1,5 +1,4 @@ package net.i2p.util; - /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain @@ -11,26 +10,93 @@ package net.i2p.util; import java.math.BigInteger; import java.util.Random; +import java.security.SecureRandom; -import net.i2p.crypto.CryptoConstants; +import java.net.URL; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.File; +/** + *
BigInteger that takes advantage of the jbigi library for the modPow operation, + * which accounts for a massive segment of the processing cost of asymmetric + * crypto. The jbigi library itself is basically just a JNI wrapper around the + * GMP library - a collection of insanely efficient routines for dealing with + * big numbers.
+ * + * There are two environmental properties for configuring this component:If jbigi.enable is set to false, this class won't even attempt to use the + * native library, but if it is set to true (or is not specified), it will first + * check the platform specific library path for the "jbigi" library, as defined by + * {@link Runtime#loadLibrary} - e.g. C:\windows\jbigi.dll or /lib/libjbigi.so. + * If that fails, it reviews the jbigi.impl environment property - if that is set, + * it checks all of the components in the CLASSPATH for the file specified and + * attempts to load it as the native library. If jbigi.impl is not set, if there + * is no matching resource, or if that resource is not a valid OS/architecture + * specific library, the NativeBigInteger will revert to using the pure java + * implementation.
+ * + *That means NativeBigInteger will not attempt to guess the correct + * platform/OS/whatever - applications using this class should define that + * property prior to referencing the NativeBigInteger (or before loading + * the JVM, of course). Alternately, people with custom built jbigi implementations + * in their OS's standard search path (LD_LIBRARY_PATH, etc) needn't bother.
+ * + *One way to deploy the native library is to create a jbigi.jar file containing + * all of the native implementations with filenames such as "win-athlon", "linux-p2", + * "freebsd-sparcv4", where those files are the OS specific libraries (the contents of + * the DLL or .so file built for those OSes / architectures). The user would then + * simply specify -Djbigi.impl=win-athlon and this component would pick up that + * library.
+ * + *Another way is to create a seperate jbigi.jar file for each platform containing + * one file - "native", where that file is the OS / architecture specific library + * implementation, as above. This way the user would download the correct jbigi.jar + * (and not all of the libraries for platforms/OSes they don't need) and would specify + * -Djbigi.impl=native.
+ * + *Running this class by itself does a basic unit test and benchmarks the + * NativeBigInteger.modPow vs. the BigInteger.modPow by running a 2Kbit op 100 + * times. At the end, if the native implementation is loaded this will output + * something like:
+ *+ * native run time: 6090ms (60ms each) + * java run time: 68067ms (673ms each) + * native = 8.947066860593239% of pure java time + *+ * + *
If the native implementation is not loaded, it will start by saying:
+ *+ * WARN: Native BigInteger library jbigi not loaded - using pure java + *+ *
Then go on to run the test, finally outputting:
+ *+ * java run time: 64653ms (640ms each) + * However, we couldn't load the native library, so this doesn't test much + *+ * + */ public class NativeBigInteger extends BigInteger { - private final static Log _log = new Log(NativeBigInteger.class); + /** did we load the native lib correctly? */ private static boolean _nativeOk = false; + /** + * do we want to dump some basic success/failure info to stderr during + * initialization? this would otherwise use the Log component, but this makes + * it easier for other systems to reuse this class + */ + private static final boolean _doLog = true; + static { - try { - System.loadLibrary("jbigi"); - _nativeOk = true; - _log.info("Native BigInteger library jbigi loaded"); - } catch (UnsatisfiedLinkError ule) { - _nativeOk = false; - _log.log(Log.CRIT, "Native BigInteger library jbigi not loaded - using pure java"); - _log.warn("jbigi not loaded", ule); - } + loadNative(); } /** - * calculate (base ^ exponent) % modulus. + * calculate (base ^ exponent) % modulus. * @param base big endian twos complement representation of the base (but it must be positive) * @param exponent big endian twos complement representation of the exponent * @param modulus big endian twos complement representation of the modulus @@ -68,34 +134,48 @@ public class NativeBigInteger extends BigInteger { else return super.modPow(exponent, m); } - + + /** + *
Compare the BigInteger.modPow vs the NativeBigInteger.modPow of some + * really big (2Kbit) numbers 100 different times and benchmark the + * performance (or shit a brick if they don't match).
+ * + */ public static void main(String args[]) { - if (_nativeOk) - System.out.println("Native library loaded"); - else - System.out.println("Native library NOT loaded"); - System.out.println("Warming up the random number generator..."); - RandomSource.getInstance().nextBoolean(); - System.out.println("Random number generator warmed up"); + runTest(100); + } - int numRuns = 100; - if (args.length == 1) { - try { - numRuns = Integer.parseInt(args[0]); - } catch (NumberFormatException nfe) { - } - } - BigInteger jg = new BigInteger(CryptoConstants.elgg.toByteArray()); - BigInteger jp = new BigInteger(CryptoConstants.elgp.toByteArray()); + /* the sample numbers are elG generator/prime so we can test with reasonable numbers */ + private final static byte[] _sampleGenerator = new BigInteger("2").toByteArray(); + private final static byte[] _samplePrime = new BigInteger("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" + + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" + + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" + + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" + + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" + + "C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" + + "83655D23DCA3AD961C62F356208552BB9ED529077096966D" + + "670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B" + + "E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9" + + "DE2BCBF6955817183995497CEA956AE515D2261898FA0510" + + "15728E5A8AACAA68FFFFFFFFFFFFFFFF", 16).toByteArray(); + + private static void runTest(int numRuns) { + System.out.println("DEBUG: Warming up the random number generator..."); + SecureRandom rand = new SecureRandom(); + rand.nextBoolean(); + System.out.println("DEBUG: Random number generator warmed up"); + + BigInteger jg = new BigInteger(_sampleGenerator); + BigInteger jp = new BigInteger(_samplePrime); long totalTime = 0; long javaTime = 0; int runsProcessed = 0; for (runsProcessed = 0; runsProcessed < numRuns; runsProcessed++) { - BigInteger bi = new BigInteger(2048, RandomSource.getInstance()); - NativeBigInteger g = new NativeBigInteger(CryptoConstants.elgg.toByteArray()); - NativeBigInteger p = new NativeBigInteger(CryptoConstants.elgp.toByteArray()); + BigInteger bi = new BigInteger(2048, rand); + NativeBigInteger g = new NativeBigInteger(_sampleGenerator); + NativeBigInteger p = new NativeBigInteger(_samplePrime); NativeBigInteger k = new NativeBigInteger(1, bi.toByteArray()); long beforeModPow = System.currentTimeMillis(); BigInteger myValue = g.modPow(k, p); @@ -106,31 +186,141 @@ public class NativeBigInteger extends BigInteger { totalTime += (afterModPow - beforeModPow); javaTime += (afterJavaModPow - afterModPow); if (!myValue.equals(jval)) { - _log.error("[" + runsProcessed + "]\tnative modPow != java modPow"); - _log.error("native modPow value: " + myValue.toString()); - _log.error("java modPow value: " + jval.toString()); - _log.error("run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); - System.err.println("[" + runsProcessed + "]\tnative modPow != java modPow"); + System.err.println("ERROR: [" + runsProcessed + "]\tnative modPow != java modPow"); + System.err.println("ERROR: native modPow value: " + myValue.toString()); + System.err.println("ERROR: java modPow value: " + jval.toString()); + System.err.println("ERROR: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); break; } else { - _log.debug("current run time: " + (afterModPow - beforeModPow) + "ms (total: " + totalTime + "ms, " - + (totalTime / (runsProcessed + 1)) + "ms each)"); + System.out.println("DEBUG: current run time: " + (afterModPow - beforeModPow) + "ms (total: " + + totalTime + "ms, " + (totalTime / (runsProcessed + 1)) + "ms each)"); } } - _log.info(numRuns + " runs complete without any errors"); - _log.info("run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); + System.out.println("INFO: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); if (numRuns == runsProcessed) - System.out.println(runsProcessed + " runs complete without any errors"); + System.out.println("INFO: " + runsProcessed + " runs complete without any errors"); else - System.out.println(runsProcessed + " runs until we got an error"); + System.out.println("ERROR: " + runsProcessed + " runs until we got an error"); if (_nativeOk) { System.out.println("native run time: \t" + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); - System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)"); + System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)"); System.out.println("native = " + ((totalTime * 100.0d) / (double) javaTime) + "% of pure java time"); } else { System.out.println("java run time: \t" + javaTime + "ms (" + (javaTime / (runsProcessed + 1)) + "ms each)"); + System.out.println("However, we couldn't load the native library, so this doesn't test much"); + } + } + + /** + *Do whatever we can to load up the native library backing this BigInteger's modPow. + * If it can find a custom built jbigi.dll / libjbigi.so, it'll use that. Otherwise + * it'll try to look in the classpath for the correct library (see loadFromResource). + * If the user specifies -Djbigi.enable=false it'll skip all of this.
+ * + */ + private static final void loadNative() { + String wantedProp = System.getProperty("jbigi.enable", "true"); + boolean wantNative = "true".equalsIgnoreCase(wantedProp); + if (wantNative) { + boolean loaded = loadGeneric(); + if (loaded) { + _nativeOk = true; + if (_doLog) + System.err.println("INFO: Native BigInteger library jbigi loaded"); + } else { + loaded = loadFromResource(); + if (loaded) { + _nativeOk = true; + if (_doLog) + System.err.println("INFO: Native BigInteger library jbigi loaded from resource"); + } else { + _nativeOk = false; + if (_doLog) + System.err.println("WARN: Native BigInteger library jbigi not loaded - using pure java"); + } + } + } else { + if (_doLog) + System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java"); + } + } + + /** + *Try loading it from an explictly build jbigi.dll / libjbigi.so first, before + * looking into a jbigi.jar for any other libraries.
+ * + * @return true if it was loaded successfully, else false + * + */ + private static final boolean loadGeneric() { + try { + System.loadLibrary("jbigi"); + return true; + } catch (UnsatisfiedLinkError ule) { + return false; + } + } + + /** + *Check all of the jars in the classpath for the file specified by the + * environmental property "jbigi.impl" and load it as the native library + * implementation. For instance, a windows user on a p4 would define + * -Djbigi.impl=win-686 if there is a jbigi.jar in the classpath containing the + * files "win-686", "win-athlon", "freebsd-p4", "linux-p3", where each + * of those files contain the correct binary file for a native library (e.g. + * windows DLL, or a *nix .so).
+ * + *This is a pretty ugly hack, using the general technique illustrated by the + * onion FEC libraries. It works by pulling the resource, writing out the + * byte stream to a temporary file, loading the native library from that file, + * then deleting the file.
+ * + * @return true if it was loaded successfully, else false + * + */ + private static final boolean loadFromResource() { + String resourceName = System.getProperty("jbigi.impl"); + if (resourceName == null) return false; + URL resource = NativeBigInteger.class.getClassLoader().getResource(resourceName); + if (resource == null) { + if (_doLog) + System.err.println("ERROR: Resource name [" + resourceName + "] was not found"); + return false; + } + + File outFile = null; + try { + InputStream libStream = resource.openStream(); + outFile = File.createTempFile("jbigi", "lib.tmp"); + FileOutputStream fos = new FileOutputStream(outFile); + byte buf[] = new byte[4096*1024]; + while (true) { + int read = libStream.read(buf); + if (read < 0) break; + fos.write(buf, 0, read); + } + fos.close(); + System.load(outFile.getPath()); + return true; + } catch (UnsatisfiedLinkError ule) { + if (_doLog) { + System.err.println("ERROR: The resource " + resourceName + + " was not a valid library for this platform"); + ule.printStackTrace(); + } + return false; + } catch (IOException ioe) { + if (_doLog) { + System.err.println("ERROR: Problem writing out the temporary native library data"); + ioe.printStackTrace(); + } + return false; + } finally { + if (outFile != null) { + outFile.deleteOnExit(); + } } } } \ No newline at end of file