├── README ├── LICENSE └── src └── org └── litecoinpool └── miner ├── Miner.java ├── Hasher.java ├── Worker.java └── Work.java /README: -------------------------------------------------------------------------------- 1 | A pure-Java sCrypt miner for Litecoin. 2 | https://bitcointalk.org/index.php?topic=52386.0 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 LitecoinPool.org 2 | 3 | This program is free software; you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License, version 2, as 5 | published by the Free Software Foundation. 6 | 7 | This program is distributed in the hope that it will be useful, 8 | but WITHOUT ANY WARRANTY; without even the implied warranty of 9 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 | GNU General Public License for more details. 11 | 12 | You should have received a copy of the GNU General Public License 13 | along with this program; if not, write to the Free Software 14 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 15 | -------------------------------------------------------------------------------- /src/org/litecoinpool/miner/Miner.java: -------------------------------------------------------------------------------- 1 | package org.litecoinpool.miner; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.Date; 8 | import java.util.Observable; 9 | import java.util.Observer; 10 | 11 | public class Miner implements Observer { 12 | 13 | private static final String DEFAULT_URL = "http://127.0.0.1:9332/"; 14 | private static final String DEFAULT_AUTH = "rpcuser:rpcpass"; 15 | private static final long DEFAULT_SCAN_TIME = 5000; 16 | private static final long DEFAULT_RETRY_PAUSE = 30000; 17 | 18 | private Worker worker; 19 | private long lastWorkTime; 20 | private long lastWorkHashes; 21 | 22 | public Miner(String url, String auth, long scanTime, long retryPause, int nThread, double throttle) { 23 | if (nThread < 1) 24 | throw new IllegalArgumentException("Invalid number of threads: " + nThread); 25 | if (throttle <= 0.0 || throttle > 1.0) 26 | throw new IllegalArgumentException("Invalid throttle: " + throttle); 27 | if (scanTime < 1L) 28 | throw new IllegalArgumentException("Invalid scan time: " + scanTime); 29 | if (retryPause < 0L) 30 | throw new IllegalArgumentException("Invalid retry pause: " + retryPause); 31 | try { 32 | worker = new Worker(new URL(url), auth, scanTime, retryPause, nThread, throttle); 33 | } catch (MalformedURLException e) { 34 | throw new IllegalArgumentException("Invalid URL: " + url); 35 | } 36 | worker.addObserver(this); 37 | Thread t = new Thread(worker); 38 | t.setPriority(Thread.MIN_PRIORITY); 39 | t.start(); 40 | log(nThread + " miner threads started"); 41 | } 42 | 43 | private static final DateFormat logDateFormat = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss] "); 44 | 45 | public void log(String str) { 46 | System.out.println(logDateFormat.format(new Date()) + str); 47 | } 48 | 49 | public void update(Observable o, Object arg) { 50 | Worker.Notification n = (Worker.Notification) arg; 51 | if (n == Worker.Notification.SYSTEM_ERROR) { 52 | log("System error"); 53 | System.exit(1); 54 | } else if (n == Worker.Notification.PERMISSION_ERROR) { 55 | log("Permission error"); 56 | System.exit(1); 57 | } else if (n == Worker.Notification.AUTHENTICATION_ERROR) { 58 | log("Invalid worker username or password"); 59 | System.exit(1); 60 | } else if (n == Worker.Notification.CONNECTION_ERROR) { 61 | log("Connection error, retrying in " + worker.getRetryPause()/1000L + " seconds"); 62 | } else if (n == Worker.Notification.COMMUNICATION_ERROR) { 63 | log("Communication error"); 64 | } else if (n == Worker.Notification.LONG_POLLING_FAILED) { 65 | log("Long polling failed"); 66 | } else if (n == Worker.Notification.LONG_POLLING_ENABLED) { 67 | log("Long polling activated"); 68 | } else if (n == Worker.Notification.NEW_BLOCK_DETECTED) { 69 | log("LONGPOLL detected new block"); 70 | } else if (n == Worker.Notification.POW_TRUE) { 71 | log("PROOF OF WORK RESULT: true (yay!!!)"); 72 | } else if (n == Worker.Notification.POW_FALSE) { 73 | log("PROOF OF WORK RESULT: false (booooo)"); 74 | } else if (n == Worker.Notification.NEW_WORK) { 75 | if (lastWorkTime > 0L) { 76 | long hashes = worker.getHashes() - lastWorkHashes; 77 | float speed = (float) hashes / Math.max(1, System.currentTimeMillis() - lastWorkTime); 78 | log(String.format("%d hashes, %.2f khash/s", hashes, speed)); 79 | } 80 | lastWorkTime = System.currentTimeMillis(); 81 | lastWorkHashes = worker.getHashes(); 82 | } 83 | } 84 | 85 | public static void main(String[] args) { 86 | String url = DEFAULT_URL; 87 | String auth = DEFAULT_AUTH; 88 | int nThread = Runtime.getRuntime().availableProcessors(); 89 | double throttle = 1.0; 90 | long scanTime = DEFAULT_SCAN_TIME; 91 | long retryPause = DEFAULT_RETRY_PAUSE; 92 | 93 | if (args.length > 0 && args[0].equals("--help")) { 94 | System.out.println("Usage: java Miner [URL] [USERNAME:PASSWORD] [THREADS] [THROTTLE] [SCANTIME] [RETRYPAUSE]"); 95 | return; 96 | } 97 | 98 | if (args.length > 0) url = args[0]; 99 | if (args.length > 1) auth = args[1]; 100 | if (args.length > 2) nThread = Integer.parseInt(args[2]); 101 | if (args.length > 3) throttle = Double.parseDouble(args[3]); 102 | if (args.length > 4) scanTime = Integer.parseInt(args[4]) * 1000L; 103 | if (args.length > 5) retryPause = Integer.parseInt(args[5]) * 1000L; 104 | 105 | try { 106 | new Miner(url, auth, scanTime, retryPause, nThread, throttle); 107 | } catch (Exception e) { 108 | System.err.println(e.getMessage()); 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /src/org/litecoinpool/miner/Hasher.java: -------------------------------------------------------------------------------- 1 | package org.litecoinpool.miner; 2 | import javax.crypto.Mac; 3 | import javax.crypto.spec.SecretKeySpec; 4 | import java.security.GeneralSecurityException; 5 | 6 | import static java.lang.System.arraycopy; 7 | import static java.lang.Integer.rotateLeft; 8 | 9 | public class Hasher { 10 | 11 | private Mac mac; 12 | private byte[] H = new byte[32]; 13 | private byte[] B = new byte[128 + 4]; 14 | private int[] X = new int[32]; 15 | private int[] V = new int[32 * 1024]; 16 | 17 | public Hasher() throws GeneralSecurityException { 18 | mac = Mac.getInstance("HmacSHA256"); 19 | } 20 | 21 | public byte[] hash(byte[] header) throws GeneralSecurityException { 22 | return hash(header, header[76] | header[77] << 8 | header[78] << 16 | header[79] << 24); 23 | } 24 | 25 | public byte[] hash(byte[] header, int nonce) throws GeneralSecurityException { 26 | int i, j, k; 27 | 28 | arraycopy(header, 0, B, 0, 76); 29 | B[76] = (byte) (nonce >> 0); 30 | B[77] = (byte) (nonce >> 8); 31 | B[78] = (byte) (nonce >> 16); 32 | B[79] = (byte) (nonce >> 24); 33 | mac.init(new SecretKeySpec(B, 0, 80, "HmacSHA256")); 34 | B[80] = 0; 35 | B[81] = 0; 36 | B[82] = 0; 37 | for (i = 0; i < 4; i++) { 38 | B[83] = (byte) (i + 1); 39 | mac.update(B, 0, 84); 40 | mac.doFinal(H, 0); 41 | 42 | for (j = 0; j < 8; j++) { 43 | X[i * 8 + j] = (H[j * 4 + 0] & 0xff) << 0 44 | | (H[j * 4 + 1] & 0xff) << 8 45 | | (H[j * 4 + 2] & 0xff) << 16 46 | | (H[j * 4 + 3] & 0xff) << 24; 47 | } 48 | } 49 | 50 | for (i = 0; i < 1024; i++) { 51 | arraycopy(X, 0, V, i * 32, 32); 52 | xorSalsa8(0, 16); 53 | xorSalsa8(16, 0); 54 | } 55 | for (i = 0; i < 1024; i++) { 56 | k = (X[16] & 1023) * 32; 57 | for (j = 0; j < 32; j++) 58 | X[j] ^= V[k + j]; 59 | xorSalsa8(0, 16); 60 | xorSalsa8(16, 0); 61 | } 62 | 63 | for (i = 0; i < 32; i++) { 64 | B[i * 4 + 0] = (byte) (X[i] >> 0); 65 | B[i * 4 + 1] = (byte) (X[i] >> 8); 66 | B[i * 4 + 2] = (byte) (X[i] >> 16); 67 | B[i * 4 + 3] = (byte) (X[i] >> 24); 68 | } 69 | 70 | B[128 + 3] = 1; 71 | mac.update(B, 0, 128 + 4); 72 | mac.doFinal(H, 0); 73 | 74 | return H; 75 | } 76 | 77 | private void xorSalsa8(int di, int xi) { 78 | int x00 = (X[di + 0] ^= X[xi + 0]); 79 | int x01 = (X[di + 1] ^= X[xi + 1]); 80 | int x02 = (X[di + 2] ^= X[xi + 2]); 81 | int x03 = (X[di + 3] ^= X[xi + 3]); 82 | int x04 = (X[di + 4] ^= X[xi + 4]); 83 | int x05 = (X[di + 5] ^= X[xi + 5]); 84 | int x06 = (X[di + 6] ^= X[xi + 6]); 85 | int x07 = (X[di + 7] ^= X[xi + 7]); 86 | int x08 = (X[di + 8] ^= X[xi + 8]); 87 | int x09 = (X[di + 9] ^= X[xi + 9]); 88 | int x10 = (X[di + 10] ^= X[xi + 10]); 89 | int x11 = (X[di + 11] ^= X[xi + 11]); 90 | int x12 = (X[di + 12] ^= X[xi + 12]); 91 | int x13 = (X[di + 13] ^= X[xi + 13]); 92 | int x14 = (X[di + 14] ^= X[xi + 14]); 93 | int x15 = (X[di + 15] ^= X[xi + 15]); 94 | for (int i = 0; i < 8; i += 2) { 95 | x04 ^= rotateLeft(x00+x12, 7); x08 ^= rotateLeft(x04+x00, 9); 96 | x12 ^= rotateLeft(x08+x04,13); x00 ^= rotateLeft(x12+x08,18); 97 | x09 ^= rotateLeft(x05+x01, 7); x13 ^= rotateLeft(x09+x05, 9); 98 | x01 ^= rotateLeft(x13+x09,13); x05 ^= rotateLeft(x01+x13,18); 99 | x14 ^= rotateLeft(x10+x06, 7); x02 ^= rotateLeft(x14+x10, 9); 100 | x06 ^= rotateLeft(x02+x14,13); x10 ^= rotateLeft(x06+x02,18); 101 | x03 ^= rotateLeft(x15+x11, 7); x07 ^= rotateLeft(x03+x15, 9); 102 | x11 ^= rotateLeft(x07+x03,13); x15 ^= rotateLeft(x11+x07,18); 103 | x01 ^= rotateLeft(x00+x03, 7); x02 ^= rotateLeft(x01+x00, 9); 104 | x03 ^= rotateLeft(x02+x01,13); x00 ^= rotateLeft(x03+x02,18); 105 | x06 ^= rotateLeft(x05+x04, 7); x07 ^= rotateLeft(x06+x05, 9); 106 | x04 ^= rotateLeft(x07+x06,13); x05 ^= rotateLeft(x04+x07,18); 107 | x11 ^= rotateLeft(x10+x09, 7); x08 ^= rotateLeft(x11+x10, 9); 108 | x09 ^= rotateLeft(x08+x11,13); x10 ^= rotateLeft(x09+x08,18); 109 | x12 ^= rotateLeft(x15+x14, 7); x13 ^= rotateLeft(x12+x15, 9); 110 | x14 ^= rotateLeft(x13+x12,13); x15 ^= rotateLeft(x14+x13,18); 111 | } 112 | X[di + 0] += x00; 113 | X[di + 1] += x01; 114 | X[di + 2] += x02; 115 | X[di + 3] += x03; 116 | X[di + 4] += x04; 117 | X[di + 5] += x05; 118 | X[di + 6] += x06; 119 | X[di + 7] += x07; 120 | X[di + 8] += x08; 121 | X[di + 9] += x09; 122 | X[di + 10] += x10; 123 | X[di + 11] += x11; 124 | X[di + 12] += x12; 125 | X[di + 13] += x13; 126 | X[di + 14] += x14; 127 | X[di + 15] += x15; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/org/litecoinpool/miner/Worker.java: -------------------------------------------------------------------------------- 1 | package org.litecoinpool.miner; 2 | import java.io.IOException; 3 | import java.net.HttpURLConnection; 4 | import java.net.SocketTimeoutException; 5 | import java.net.URL; 6 | import java.security.AccessControlException; 7 | import java.security.GeneralSecurityException; 8 | import java.util.Observable; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | import java.util.concurrent.locks.LockSupport; 11 | 12 | public class Worker extends Observable implements Runnable { 13 | 14 | private static final long WORK_TIMEOUT = 60 * 1000; // ms 15 | 16 | public static enum Notification { 17 | SYSTEM_ERROR, 18 | PERMISSION_ERROR, 19 | CONNECTION_ERROR, 20 | AUTHENTICATION_ERROR, 21 | COMMUNICATION_ERROR, 22 | LONG_POLLING_FAILED, 23 | LONG_POLLING_ENABLED, 24 | NEW_BLOCK_DETECTED, 25 | NEW_WORK, 26 | POW_TRUE, 27 | POW_FALSE, 28 | TERMINATED 29 | }; 30 | 31 | private URL url; 32 | private String auth; 33 | private long scanTime; // ms 34 | private long retryPause; // ms 35 | private int nThreads; 36 | private double throttleFactor; 37 | 38 | private volatile Work curWork = null; 39 | private URL lpUrl = null; 40 | private HttpURLConnection lpConn = null; 41 | private AtomicLong hashes = new AtomicLong(0L); 42 | 43 | public Worker(URL url, String auth, long scanMillis, long pauseMillis) { 44 | this(url, auth, scanMillis, pauseMillis, Runtime.getRuntime().availableProcessors()); 45 | } 46 | 47 | public Worker(URL url, String auth, long scanMillis, long pauseMillis, int nThreads) { 48 | this(url, auth, scanMillis, pauseMillis, nThreads, 1.0); 49 | } 50 | 51 | public Worker(URL url, String auth, long scanMillis, long pauseMillis, int nThreads, double throttle) { 52 | this.url = url; 53 | this.auth = auth; 54 | this.scanTime = scanMillis; 55 | this.retryPause = pauseMillis; 56 | if (nThreads < 0) 57 | throw new IllegalArgumentException(); 58 | this.nThreads = nThreads; 59 | if (throttle <= 0.0 || throttle > 1.0) 60 | throw new IllegalArgumentException(); 61 | this.throttleFactor = 1.0 / throttle - 1.0; 62 | } 63 | 64 | public long getRetryPause() { 65 | return retryPause; 66 | } 67 | 68 | public long getHashes() { 69 | return hashes.get(); 70 | } 71 | 72 | private volatile boolean running = false; 73 | 74 | public synchronized void stop() { 75 | running = false; 76 | this.notifyAll(); 77 | } 78 | 79 | public void run() { 80 | Thread[] threads; 81 | running = true; 82 | synchronized (this) { 83 | threads = new Thread[1 + nThreads]; 84 | for (int i = 0; i < nThreads; ++i) 85 | (threads[1 + i] = new Thread(new WorkChecker(i))).start(); 86 | do { 87 | try { 88 | if (curWork == null || curWork.getAge() >= WORK_TIMEOUT || lpUrl == null) { 89 | curWork = getWork(); 90 | if (lpUrl == null) { 91 | try { 92 | if ((lpUrl = curWork.getLongPollingURL()) != null) { 93 | (threads[0] = new Thread(new LongPoller())).start(); 94 | setChanged(); 95 | notifyObservers(Notification.LONG_POLLING_ENABLED); 96 | } 97 | } catch (Exception e) { } 98 | } 99 | setChanged(); 100 | notifyObservers(Notification.NEW_WORK); 101 | } 102 | if (!running) 103 | break; 104 | this.wait(Math.min(scanTime, Math.max(1L, WORK_TIMEOUT - curWork.getAge()))); 105 | } catch (InterruptedException e) { 106 | } catch (NullPointerException e) { } 107 | } while (running); 108 | running = false; 109 | } 110 | if (lpConn != null) 111 | lpConn.disconnect(); 112 | try { 113 | for (Thread t : threads) 114 | if (t != null) 115 | t.join(); 116 | } catch (InterruptedException e) { } 117 | curWork = null; 118 | setChanged(); 119 | notifyObservers(Notification.TERMINATED); 120 | } 121 | 122 | private synchronized Work getWork() { 123 | while (running) { 124 | try { 125 | return new Work(url, auth); 126 | } catch (Exception e) { 127 | if (!running) 128 | break; 129 | setChanged(); 130 | if (e instanceof IllegalArgumentException) { 131 | notifyObservers(Notification.AUTHENTICATION_ERROR); 132 | stop(); 133 | break; 134 | } else if (e instanceof AccessControlException) { 135 | notifyObservers(Notification.PERMISSION_ERROR); 136 | stop(); 137 | break; 138 | } else if (e instanceof IOException) { 139 | notifyObservers(Notification.CONNECTION_ERROR); 140 | } else { 141 | notifyObservers(Notification.COMMUNICATION_ERROR); 142 | } 143 | try { 144 | curWork = null; 145 | this.wait(retryPause); 146 | } catch (InterruptedException ie) { } 147 | } 148 | } 149 | return null; 150 | } 151 | 152 | private class LongPoller implements Runnable { 153 | private static final int READ_TIMEOUT = 30 * 60 * 1000; // ms 154 | public void run() { 155 | while (running) { 156 | try { 157 | lpConn = (HttpURLConnection) lpUrl.openConnection(); 158 | lpConn.setReadTimeout(READ_TIMEOUT); 159 | curWork = new Work(lpConn, url, auth); 160 | if (!running) 161 | break; 162 | synchronized (Worker.this) { 163 | setChanged(); 164 | notifyObservers(Notification.NEW_BLOCK_DETECTED); 165 | setChanged(); 166 | notifyObservers(Notification.NEW_WORK); 167 | //Worker.this.notify(); 168 | } 169 | } catch (SocketTimeoutException e) { 170 | } catch (Exception e) { 171 | if (!running) 172 | break; 173 | setChanged(); 174 | notifyObservers(Notification.LONG_POLLING_FAILED); 175 | try { 176 | Thread.sleep(retryPause); 177 | } catch (InterruptedException ie) { } 178 | } 179 | } 180 | lpUrl = null; 181 | lpConn = null; 182 | } 183 | } 184 | 185 | private class WorkChecker implements Runnable { 186 | private static final long THROTTLE_WAIT_TIME = 100L * 1000000L; // ns 187 | private int index; 188 | private int step; 189 | public WorkChecker(int index) { 190 | this.index = index; 191 | for (step = 1; step < nThreads; step <<= 1); 192 | } 193 | public void run() { 194 | try { 195 | Hasher hasher = new Hasher(); 196 | int nonce = index; 197 | long dt, t0 = System.nanoTime(); 198 | while (running) { 199 | try { 200 | if (curWork.meetsTarget(nonce, hasher)) { 201 | new Thread(new WorkSubmitter(curWork, nonce)).start(); 202 | if (lpUrl == null) { 203 | synchronized (Worker.this) { 204 | curWork = null; 205 | Worker.this.notify(); 206 | } 207 | } 208 | } 209 | nonce += step; 210 | hashes.incrementAndGet(); 211 | if (throttleFactor > 0.0 && (dt = System.nanoTime() - t0) > THROTTLE_WAIT_TIME) { 212 | LockSupport.parkNanos(Math.max(0L, (long) (throttleFactor * dt))); 213 | t0 = System.nanoTime(); 214 | } 215 | } catch (NullPointerException e) { 216 | try { 217 | Thread.sleep(1L); 218 | } catch (InterruptedException ie) { } 219 | } 220 | } 221 | } catch (GeneralSecurityException e) { 222 | setChanged(); 223 | notifyObservers(Notification.SYSTEM_ERROR); 224 | stop(); 225 | } 226 | } 227 | } 228 | 229 | private class WorkSubmitter implements Runnable { 230 | private Work work; 231 | private int nonce; 232 | public WorkSubmitter(Work w, int nonce) { 233 | this.work = w; 234 | this.nonce = nonce; 235 | } 236 | public void run() { 237 | try { 238 | boolean result = work.submit(nonce); 239 | setChanged(); 240 | notifyObservers(result ? Notification.POW_TRUE : Notification.POW_FALSE); 241 | } catch (IOException e) { } 242 | } 243 | } 244 | 245 | } 246 | -------------------------------------------------------------------------------- /src/org/litecoinpool/miner/Work.java: -------------------------------------------------------------------------------- 1 | package org.litecoinpool.miner; 2 | import java.io.ByteArrayOutputStream; 3 | import java.io.DataOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.net.HttpURLConnection; 7 | import java.net.MalformedURLException; 8 | import java.net.URISyntaxException; 9 | import java.net.URL; 10 | import java.security.GeneralSecurityException; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | public class Work { 15 | 16 | private static final int DEFAULT_TIMEOUT = 10000; // ms 17 | 18 | private static final Pattern dataPattern = Pattern.compile("\"data\"\\s*:\\s*\"([0-9a-f]+)\""); 19 | private static final Pattern targetPattern = Pattern.compile("\"target\"\\s*:\\s*\"([0-9a-f]+)\""); 20 | private static final Pattern resultPattern = Pattern.compile("\"result\"\\s*:\\s*([0-9A-Za-z]+)"); 21 | 22 | private URL url; 23 | private String auth; 24 | private long responseTime; 25 | private String xLongPolling = null; 26 | 27 | private byte[] data; // little-endian 28 | private byte[] target; // little-endian 29 | private byte[] header; // big-endian 30 | 31 | public Work(URL url, String auth) throws IOException { 32 | this(url, url, auth); 33 | } 34 | 35 | public Work(URL url, URL mainUrl, String auth) throws IOException { 36 | this((HttpURLConnection) url.openConnection(), url, auth); 37 | } 38 | 39 | public Work(HttpURLConnection conn, URL mainUrl, String auth) throws IOException { 40 | String request = "{\"method\": \"getwork\", \"params\": [], \"id\":0}"; 41 | 42 | conn = getJsonRpcConnection(conn, request, auth); 43 | int response = conn.getResponseCode(); 44 | if (response == 401 || response == 403) 45 | throw new IllegalArgumentException("Access denied"); 46 | String content = getConnectionContent(conn); 47 | 48 | responseTime = System.currentTimeMillis(); 49 | Matcher m = dataPattern.matcher(content); 50 | if (!m.find()) 51 | throw new RuntimeException(content); 52 | String sData = m.group(1); 53 | data = hexStringToByteArray(sData); 54 | m = targetPattern.matcher(content); 55 | if (!m.find()) 56 | throw new RuntimeException(content); 57 | String sTarget = m.group(1); 58 | target= hexStringToByteArray(sTarget); 59 | header = headerByData(data); 60 | xLongPolling = conn.getHeaderField("X-Long-Polling"); 61 | this.url = mainUrl; 62 | this.auth = auth; 63 | } 64 | 65 | public boolean submit(int nonce) throws IOException { 66 | byte[] d = data.clone(); 67 | d[79] = (byte) (nonce >> 0); 68 | d[78] = (byte) (nonce >> 8); 69 | d[77] = (byte) (nonce >> 16); 70 | d[76] = (byte) (nonce >> 24); 71 | String sData = byteArrayToHexString(d); 72 | String request = "{\"method\": \"getwork\", \"params\": [ \"" + sData + "\" ], \"id\":1}"; 73 | 74 | HttpURLConnection conn = getJsonRpcConnection(url, request, auth); 75 | String content = getConnectionContent(conn); 76 | 77 | Matcher m = resultPattern.matcher(content); 78 | if (m.find() && m.group(1).equals("true")) 79 | return true; 80 | return false; 81 | } 82 | 83 | public boolean meetsTarget(int nonce, Hasher hasher) throws GeneralSecurityException { 84 | byte[] hash = hasher.hash(header, nonce); 85 | for (int i = hash.length - 1; i >= 0; i--) { 86 | if ((hash[i] & 0xff) > (target[i] & 0xff)) 87 | return false; 88 | if ((hash[i] & 0xff) < (target[i] & 0xff)) 89 | return true; 90 | } 91 | return true; 92 | } 93 | 94 | public byte[] getData() { 95 | return data; 96 | } 97 | 98 | public byte[] getTarget() { 99 | return target; 100 | } 101 | 102 | public byte[] getHeader() { 103 | return header; 104 | } 105 | 106 | public long getResponseTime() { 107 | return responseTime; 108 | } 109 | 110 | public long getAge() { 111 | return System.currentTimeMillis() - responseTime; 112 | } 113 | 114 | public URL getLongPollingURL() throws URISyntaxException, MalformedURLException { 115 | if (xLongPolling == null) 116 | return null; 117 | return url.toURI().resolve(xLongPolling).toURL(); 118 | } 119 | 120 | private static byte[] headerByData(byte[] data) { 121 | byte[] h = new byte[80]; 122 | for (int i = 0; i < 80; i += 4) { 123 | h[i] = data[i + 3]; 124 | h[i + 1] = data[i + 2]; 125 | h[i + 2] = data[i + 1]; 126 | h[i + 3] = data[i]; 127 | } 128 | return h; 129 | } 130 | 131 | public static String byteArrayToHexString(byte[] b) { 132 | StringBuilder sb = new StringBuilder(80); 133 | for (int i = 0; i < b.length; i++) 134 | sb.append(Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1)); 135 | return sb.toString(); 136 | } 137 | 138 | public static byte[] hexStringToByteArray(String s) { 139 | int len = s.length(); 140 | byte[] data = new byte[len / 2]; 141 | for (int i = 0; i < len; i += 2) { 142 | data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) 143 | + Character.digit(s.charAt(i+1), 16)); 144 | } 145 | return data; 146 | } 147 | 148 | private final static char[] BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); 149 | private static int[] toInt = new int[128]; 150 | 151 | static { 152 | for (int i = 0; i < BASE64_ALPHABET.length; i++) { 153 | toInt[BASE64_ALPHABET[i]] = i; 154 | } 155 | } 156 | 157 | public static String stringToBase64(String str) { 158 | byte[] buf = str.getBytes(); 159 | int size = buf.length; 160 | char[] ar = new char[((size + 2) / 3) * 4]; 161 | int a = 0; 162 | int i = 0; 163 | while (i < size) { 164 | byte b0 = buf[i++]; 165 | byte b1 = (i < size) ? buf[i++] : 0; 166 | byte b2 = (i < size) ? buf[i++] : 0; 167 | ar[a++] = BASE64_ALPHABET[(b0 >> 2) & 0x3f]; 168 | ar[a++] = BASE64_ALPHABET[((b0 << 4) | ((b1 & 0xFF) >> 4)) & 0x3f]; 169 | ar[a++] = BASE64_ALPHABET[((b1 << 2) | ((b2 & 0xFF) >> 6)) & 0x3f]; 170 | ar[a++] = BASE64_ALPHABET[b2 & 0x3f]; 171 | } 172 | switch (size % 3) { 173 | case 1: 174 | ar[--a] = '='; 175 | case 2: 176 | ar[--a] = '='; 177 | } 178 | return new String(ar); 179 | } 180 | 181 | public static HttpURLConnection getJsonRpcConnection(URL url, String request, String auth) throws IOException { 182 | return getJsonRpcConnection((HttpURLConnection) url.openConnection(), request, auth); 183 | } 184 | 185 | public static HttpURLConnection getJsonRpcConnection(HttpURLConnection conn, String request, String auth) throws IOException { 186 | if (conn.getConnectTimeout() == 0) 187 | conn.setConnectTimeout(DEFAULT_TIMEOUT); 188 | if (conn.getReadTimeout() == 0) 189 | conn.setReadTimeout(DEFAULT_TIMEOUT); 190 | conn.setRequestMethod("POST"); 191 | if (auth != null) 192 | conn.setRequestProperty("Authorization", "Basic " + stringToBase64(auth)); 193 | conn.setRequestProperty("Content-Type", "application/json"); 194 | conn.setRequestProperty("Content-Length", Integer.toString(request.getBytes().length)); 195 | conn.setRequestProperty("X-Mining-Extensions", "midstate"); 196 | conn.setAllowUserInteraction(false); 197 | conn.setUseCaches(false); 198 | conn.setDoOutput(true); 199 | 200 | DataOutputStream wr = new DataOutputStream(conn.getOutputStream()); 201 | wr.writeBytes(request); 202 | wr.close(); 203 | return conn; 204 | } 205 | 206 | public static String getConnectionContent(HttpURLConnection conn) throws IOException { 207 | InputStream is = conn.getInputStream(); 208 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 209 | int len; 210 | byte[] buffer = new byte[4096]; 211 | while ((len = is.read(buffer)) != -1) { 212 | bos.write(buffer, 0, len); 213 | } 214 | String content = bos.toString(); 215 | is.close(); 216 | return content; 217 | } 218 | 219 | public static boolean test() { 220 | try { 221 | byte[] header = hexStringToByteArray("01000000f615f7ce3b4fc6b8f61e8f89aedb1d0852507650533a9e3b10b9bbcc30639f279fcaa86746e1ef52d3edb3c4ad8259920d509bd073605c9bf1d59983752a6b06b817bb4ea78e011d012d59d4"); 222 | byte[] hash = new Hasher().hash(header); 223 | return byteArrayToHexString(hash).equals("d9eb8663ffec241c2fb118adb7de97a82c803b6ff46d57667935c81001000000"); 224 | } catch (GeneralSecurityException e) { 225 | return false; 226 | } 227 | } 228 | 229 | } 230 | --------------------------------------------------------------------------------