package net.i2p.util; /* * free (adj.): unencumbered; not under the control of others * Written by jrandom in 2003 and released into the public domain * with no warranty of any kind, either expressed or implied. * It probably won't make your computer catch on fire, or eat * your children, but it might. Use at your own risk. * */ import java.math.BigInteger; import java.util.Random; import java.security.SecureRandom; import java.net.URL; import java.io.FileOutputStream; import java.io.InputStream; import java.io.IOException; import java.io.FileNotFoundException; import java.io.File; import freenet.support.HexUtil; //import freenet.support.Logger; import thaw.core.Logger; import freenet.support.CPUInformation.AMDCPUInfo; import freenet.support.CPUInformation.CPUID; import freenet.support.CPUInformation.CPUInfo; import freenet.support.CPUInformation.IntelCPUInfo; import freenet.support.CPUInformation.UnknownCPUException; /** * <p>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. It also takes advantage of the jbigi library for converting a BigInteger * value to a double. Sun's implementation of the 'doubleValue()' method is _very_ lousy. * * 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.</p> * * There are three environmental properties for configuring this component: <ul> * <li><b>jbigi.enable</b>: whether to use the native library (defaults to "true")</li> * <li><b>jbigi.impl</b>: select which resource to use as the native implementation</li> * <li><b>jbigi.ref</b>: the file specified in this parameter may contain a resource * name to override jbigi.impl (defaults to "jbigi.cfg")</li> * </ul> * * <p>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.</p> * * <p>That means <b>NativeBigInteger will not attempt to guess the correct * platform/OS/whatever</b> - applications using this class should define that * property prior to <i>referencing</i> 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.</p> * * <p>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.</p> * * <p>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.</p> * * <p>Running this class by itself does a basic unit test and benchmarks the * NativeBigInteger.modPow/doubleValue vs. the BigInteger.modPow/doubleValue by running a 2Kbit op 100 * times. At the end of each test, if the native implementation is loaded this will output * something like:</p> * <pre> * native run time: 6090ms (60ms each) * java run time: 68067ms (673ms each) * native = 8.947066860593239% of pure java time * </pre> * * <p>If the native implementation is not loaded, it will start by saying:</p> * <pre> * WARN: Native BigInteger library jbigi not loaded - using pure java * </pre> * <p>Then go on to run the test, finally outputting:</p> * <pre> * java run time: 64653ms (640ms each) * However, we couldn't load the native library, so this doesn't test much * </pre> * */ public class NativeBigInteger extends BigInteger { /** 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; private final static String JBIGI_OPTIMIZATION_K6 = "k6"; private final static String JBIGI_OPTIMIZATION_K6_2 = "k62"; private final static String JBIGI_OPTIMIZATION_K6_3 = "k63"; private final static String JBIGI_OPTIMIZATION_ATHLON = "athlon"; private final static String JBIGI_OPTIMIZATION_X86_64 = "x86_64"; private final static String JBIGI_OPTIMIZATION_X86_64_32 = "x86_64_32"; private final static String JBIGI_OPTIMIZATION_PENTIUM = "pentium"; private final static String JBIGI_OPTIMIZATION_PENTIUMMMX = "pentiummmx"; private final static String JBIGI_OPTIMIZATION_PENTIUM2 = "pentium2"; private final static String JBIGI_OPTIMIZATION_PENTIUM3 = "pentium3"; private final static String JBIGI_OPTIMIZATION_PENTIUM4 = "pentium4"; private final static String JBIGI_OPTIMIZATION_PPC = "ppc"; private final static String sCPUType; //The CPU Type to optimize for (one of the above strings) private static final long serialVersionUID = 0xc5392a97bb283dd2L; static { sCPUType = resolveCPUType(); loadNative(); } /** Tries to resolve the best type of CPU that we have an optimized jbigi-dll/so for. * @return A string containing the CPU-type or null if CPU type is unknown */ private static String resolveCPUType() { try { if(System.getProperty("os.arch").toLowerCase().matches("(i?[x0-9]86_64|amd64)")){ return JBIGI_OPTIMIZATION_X86_64; }else if(System.getProperty("os.arch").toLowerCase().matches("(ppc)")){ System.out.println("Detected PowerPC!"); return JBIGI_OPTIMIZATION_PPC; }else{ CPUInfo c = CPUID.getInfo(); if (c instanceof AMDCPUInfo) { AMDCPUInfo amdcpu = (AMDCPUInfo) c; if (amdcpu.IsAthlon64Compatible()) return JBIGI_OPTIMIZATION_X86_64_32; if (amdcpu.IsAthlonCompatible()) return JBIGI_OPTIMIZATION_ATHLON; if (amdcpu.IsK6_3_Compatible()) return JBIGI_OPTIMIZATION_K6_3; if (amdcpu.IsK6_2_Compatible()) return JBIGI_OPTIMIZATION_K6_2; if (amdcpu.IsK6Compatible()) return JBIGI_OPTIMIZATION_K6; } else { if (c instanceof IntelCPUInfo) { IntelCPUInfo intelcpu = (IntelCPUInfo) c; if (intelcpu.IsPentium4Compatible()) return JBIGI_OPTIMIZATION_PENTIUM4; if (intelcpu.IsPentium3Compatible()) return JBIGI_OPTIMIZATION_PENTIUM3; if (intelcpu.IsPentium2Compatible()) return JBIGI_OPTIMIZATION_PENTIUM2; if (intelcpu.IsPentiumMMXCompatible()) return JBIGI_OPTIMIZATION_PENTIUMMMX; if (intelcpu.IsPentiumCompatible()) return JBIGI_OPTIMIZATION_PENTIUM; } } } return null; } catch (UnknownCPUException e) { return null; //TODO: Log something here maybe.. } } /** * 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 * @return big endian twos complement representation of (base ^ exponent) % modulus */ public native static byte[] nativeModPow(byte base[], byte exponent[], byte modulus[]); /** * Converts a BigInteger byte-array to a 'double' * @param ba Big endian twos complement representation of the BigInteger to convert to a double * @return The plain double-value represented by 'ba' */ public native static double nativeDoubleValue(byte ba[]); private byte[] cachedBa=null; public NativeBigInteger(byte val[]) { super(val); // Takes up too much RAM // int targetLength = bitLength() / 8 + 1; // if(val.length == targetLength) // cachedBa = val; } public NativeBigInteger(int signum, byte magnitude[]) { super(signum, magnitude); } public NativeBigInteger(int bitlen, int certainty, Random rnd) { super(bitlen, certainty, rnd); } public NativeBigInteger(int numbits, Random rnd) { super(numbits, rnd); } public NativeBigInteger(String val) { super(val); } public NativeBigInteger(String val, int radix) { super(val, radix); } /**Creates a new NativeBigInteger with the same value * as the supplied BigInteger. Warning!, not very efficent */ public NativeBigInteger(BigInteger integer) { //Now, why doesn't sun provide a constructor //like this one in BigInteger? this(integer.toByteArray()); } public BigInteger modPow(BigInteger exponent, BigInteger m) { if (_nativeOk) return new NativeBigInteger(nativeModPow(toByteArray(), exponent.toByteArray(), m.toByteArray())); else return new NativeBigInteger(super.modPow(exponent, m)); } public byte[] toByteArray(){ if(cachedBa == null) //Since we are immutable it is safe to never update the cached ba after it has initially been generated cachedBa = super.toByteArray(); return cachedBa; } public String toString(int radix) { if(radix == 16) return toHexString(); return super.toString(radix); } public String toHexString() { byte[] buf = toByteArray(); return HexUtil.bytesToHex(buf); } public double doubleValue() { if (_nativeOk) return nativeDoubleValue(toByteArray()); else return super.doubleValue(); } /** * * @return True iff native methods will be used by this class */ public static boolean isNative(){ return _nativeOk; } /** * <p>Compare the BigInteger.modPow/doubleValue vs the NativeBigInteger.modPow/doubleValue of some * really big (2Kbit) numbers 100 different times and benchmark the * performance (or shit a brick if they don't match). </p> * */ public static void main(String args[]) { runModPowTest(100); runDoubleValueTest(100); } /* 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 runModPowTest(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, 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); long afterModPow = System.currentTimeMillis(); BigInteger jval = jg.modPow(bi, jp); long afterJavaModPow = System.currentTimeMillis(); totalTime += (afterModPow - beforeModPow); javaTime += (afterJavaModPow - afterModPow); if (!myValue.equals(jval)) { 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 { System.out.println("DEBUG: current run time: " + (afterModPow - beforeModPow) + "ms (total: " + 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("INFO: " + runsProcessed + " runs complete without any errors"); else 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("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"); } } private static void runDoubleValueTest(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); long totalTime = 0; long javaTime = 0; int MULTIPLICATOR = 50000; //Run the doubleValue() calls within a loop since they are pretty fast.. int runsProcessed = 0; for (runsProcessed = 0; runsProcessed < numRuns; runsProcessed++) { NativeBigInteger g = new NativeBigInteger(_sampleGenerator); long beforeDoubleValue = System.currentTimeMillis(); double dNative=0; for(int mult=0;mult<MULTIPLICATOR;mult++) dNative = g.doubleValue(); long afterDoubleValue = System.currentTimeMillis(); double jval=0; for(int mult=0;mult<MULTIPLICATOR;mult++) jval = jg.doubleValue(); long afterJavaDoubleValue = System.currentTimeMillis(); totalTime += (afterDoubleValue - beforeDoubleValue); javaTime += (afterJavaDoubleValue - afterDoubleValue); if (dNative!=jval) { System.err.println("ERROR: [" + runsProcessed + "]\tnative double != java double"); System.err.println("ERROR: native double value: " + dNative); System.err.println("ERROR: java double value: " + jval); System.err.println("ERROR: run time: " + totalTime + "ms (" + (totalTime / (runsProcessed + 1)) + "ms each)"); break; } else { System.out.println("DEBUG: current run time: " + (afterDoubleValue - beforeDoubleValue) + "ms (total: " + 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("INFO: " + runsProcessed + " runs complete without any errors"); else 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("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"); } } /** * <p>Do whatever we can to load up the native library backing this BigInteger's native methods. * 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.</p> * */ private static final void loadNative() { try{ String wantedProp = System.getProperty("jbigi.enable", "true"); boolean wantNative = "true".equalsIgnoreCase(wantedProp); if (wantNative) { boolean loaded = loadFromResource(true); if (loaded) { _nativeOk = true; if (_doLog) System.err.println("INFO: Optimized native BigInteger library '"+getResourceName(true)+"' loaded from resource"); } else { loaded = loadGeneric(true); if (loaded) { _nativeOk = true; if (_doLog) System.err.println("INFO: Optimized native BigInteger library '"+getMiddleName(true)+"' loaded from somewhere in the path"); } else { loaded = loadFromResource(false); if (loaded) { _nativeOk = true; if (_doLog) System.err.println("INFO: Non-optimized native BigInteger library '"+getResourceName(false)+"' loaded from resource"); } else { loaded = loadGeneric(false); if (loaded) { _nativeOk = true; if (_doLog) System.err.println("INFO: Non-optimized native BigInteger library '"+getMiddleName(false)+"' loaded from somewhere in the path"); } else { _nativeOk = false; } } } } } if (_doLog && !_nativeOk) System.err.println("INFO: Native BigInteger library jbigi not loaded - using pure java"); }catch(Throwable e){ if (_doLog) System.err.println("INFO: Native BigInteger library jbigi not loaded, reason: '"+e.getMessage()+"' - using pure java"); } } /** * <p>Try loading it from an explictly build jbigi.dll / libjbigi.so first, before * looking into a jbigi.jar for any other libraries.</p> * * @return true if it was loaded successfully, else false * */ private static final boolean loadGeneric(boolean optimized) { try { String name = getMiddleName(optimized); if(name == null) return false; System.loadLibrary(name); return true; } catch (UnsatisfiedLinkError ule) { return false; } } /** * A helper function to make loading the native library easier. * @param f The File to which to write the library * @param URL The URL of the resource * @return True is the library was loaded, false on error * @throws FileNotFoundException If the library could not be read from the reference * @throws UnsatisfiedLinkError If and only if the library is incompatible with this system */ private static final boolean tryLoadResource(File f, URL resource) throws FileNotFoundException, UnsatisfiedLinkError { InputStream is; try { is=resource.openStream(); } catch(IOException e) { throw new FileNotFoundException(); } try { FileOutputStream fos=new FileOutputStream(f); byte[] buf=new byte[4096*1024]; int read; while((read=is.read(buf))>0) fos.write(buf,0,read); fos.close(); System.load(f.getAbsolutePath()); f.deleteOnExit(); return true; } catch(IOException e) { f.delete(); } catch(UnsatisfiedLinkError ule) { f.delete(); // likely to be "noexec" if(ule.toString().toLowerCase().indexOf("not permitted")==-1) throw ule; } return false; } /** * <p>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). </p> * * <p>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.</p> * * @return true if it was loaded successfully, else false * */ private static final boolean loadFromResource(boolean optimized) { String resourceName = getResourceName(optimized); if (resourceName == null) return false; URL resource = NativeBigInteger.class.getClassLoader().getResource(resourceName); if (resource == null) { if (_doLog) System.err.println("NOTICE: Resource name [" + getResourceName(true) + "] was not found"); return false; } try { try { if(tryLoadResource(File.createTempFile("jbigi", "lib.tmp"), resource)) return true; } catch(IOException e) { } Logger.error(NativeBigInteger.class, "NativeBigInteger : Can't load from " + System.getProperty("java.io.tmpdir")); System.err.println("Can't load from " + System.getProperty("java.io.tmpdir")); if(tryLoadResource(new File("jbigi-lib.tmp"), resource)) return true; } catch(Exception fnf) { Logger.error(NativeBigInteger.class, "NativeBigInteger : Error reading jbigi resource"); System.err.println("Error reading jbigi resource"); } catch(UnsatisfiedLinkError ule) { Logger.error(NativeBigInteger.class, "NativeBigInteger : Library " + resourceName + " is not appropriate for this system."); System.err.println("Library " + resourceName + " is not appropriate for this system."); } return false; } private static final String getResourceName(boolean optimized) { String pname = NativeBigInteger.class.getPackage().getName().replace('.','/'); String pref = getLibraryPrefix(); String middle = getMiddleName(optimized); String suff = getLibrarySuffix(); if((pref == null) || (middle == null) || (suff == null)) return null; return pname+ '/' +pref+middle+ '.' +suff; } private static final String getMiddleName(boolean optimized){ String sAppend; if(optimized) { if(sCPUType == null) return null; else sAppend = '-' +sCPUType; }else sAppend = "-none"; boolean isWindows =(System.getProperty("os.name").toLowerCase().indexOf("windows") != -1); boolean isLinux =(System.getProperty("os.name").toLowerCase().indexOf("linux") != -1); boolean isFreebsd =(System.getProperty("os.name").toLowerCase().indexOf("freebsd") != -1); boolean isMacOS =(System.getProperty("os.name").toLowerCase().indexOf("mac os x") != -1); if(isWindows) return "jbigi-windows"+sAppend; // The convention on Windows if(isLinux) return "jbigi-linux"+sAppend; // The convention on linux... if(isFreebsd) return "jbigi-freebsd"+sAppend; // The convention on freebsd... if(isMacOS) return "jbigi-osx"+sAppend; // The convention on Mac OS X... throw new RuntimeException("Dont know jbigi library name for os type '"+System.getProperty("os.name")+ '\''); } private static final String getLibrarySuffix() { boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1; boolean isMacOS =(System.getProperty("os.name").toLowerCase().indexOf("mac os x") != -1); if(isWindows) return "dll"; else if(isMacOS) return "jnilib"; else return "so"; } private static final String getLibraryPrefix() { boolean isWindows =System.getProperty("os.name").toLowerCase().indexOf("windows") != -1; if(isWindows) return ""; else return "lib"; } }