package com.vaklinov.zcashui; import java.awt.Component; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.URL; import java.net.URLConnection; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.ProgressMonitorInputStream; import com.vaklinov.zcashui.OSUtil.OS_TYPE; /** * Fetches the proving key. Deliberately hardcoded. * @author zab */ public class ProvingKeyFetcher { private static final int PROVING_KEY_SIZE = 910173851; private static final String SHA256 = "8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7"; private static final String pathURL = "https://downloads.horizen.global/file/TrustedSetup/sprout-proving.key"; private static final int SPROUT_GROTH_SIZE = 725523612; private static final String SHA256SG = "b685d700c60328498fbde589c8c7c484c722b788b265b72af448a5bf0ee55b50"; private static final String pathURLSG = "https://downloads.horizen.global/file/TrustedSetup/sprout-groth16.params"; private static final int SAPLING_SPEND_SIZE = 47958396; private static final String SHA256SS = "8e48ffd23abb3a5fd9c5589204f32d9c31285a04b78096ba40a79b75677efc13"; private static final String pathURLSS = "https://downloads.horizen.global/file/TrustedSetup/sapling-spend.params"; // TODO: add backups private LanguageUtil langUtil; public void fetchIfMissing(StartupProgressDialog parent) throws IOException { langUtil = LanguageUtil.instance(); try { verifyOrFetch(parent); } catch (InterruptedIOException iox) { JOptionPane.showMessageDialog(parent, langUtil.getString("proving.key.fetcher.option.pane.message")); System.exit(-3); } } private void verifyOrFetch(StartupProgressDialog parent) throws IOException { OS_TYPE ost = OSUtil.getOSType(); File zCashParams = null; // TODO: isolate getting ZcashParams in a utility method if (ost == OS_TYPE.WINDOWS) { zCashParams = new File(System.getenv("APPDATA") + "/ZcashParams"); } else if (ost == OS_TYPE.MAC_OS) { File userHome = new File(System.getProperty("user.home")); zCashParams = new File(userHome, "Library/Application Support/ZcashParams"); } zCashParams = zCashParams.getCanonicalFile(); boolean needsFetch = false; boolean needsFetchSG = false; boolean needsFetchSS = false; if (!zCashParams.exists()) { needsFetch = true; needsFetchSG = true; needsFetchSS = true; zCashParams.mkdirs(); } // verifying key is small, always copy it File verifyingKeyFile = new File(zCashParams,"sprout-verifying.key"); FileOutputStream fos = new FileOutputStream(verifyingKeyFile); InputStream is = ProvingKeyFetcher.class.getClassLoader().getResourceAsStream("keys/sprout-verifying.key"); copy(is,fos); fos.close(); is = null; // sapling output params is small, always copy it File saplingOutputFile = new File(zCashParams,"sapling-output.params"); FileOutputStream fosB = new FileOutputStream(saplingOutputFile); InputStream isB = ProvingKeyFetcher.class.getClassLoader().getResourceAsStream("keys/sapling-output.params"); copy(isB,fosB); fosB.close(); isB = null; File provingKeyFile = new File(zCashParams,"sprout-proving.key"); provingKeyFile = provingKeyFile.getCanonicalFile(); File sproutGrothFile = new File(zCashParams,"sprout-groth16.params"); sproutGrothFile = sproutGrothFile.getCanonicalFile(); File saplingSpendFile = new File(zCashParams,"sapling-spend.params"); saplingSpendFile = saplingSpendFile.getCanonicalFile(); if (!provingKeyFile.exists()) { needsFetch = true; } else if (provingKeyFile.length() != PROVING_KEY_SIZE) { needsFetch = true; } if (!sproutGrothFile.exists()) { needsFetchSG = true; } else if (sproutGrothFile.length() != SPROUT_GROTH_SIZE) { needsFetchSG = true; } if (!saplingSpendFile.exists()) { needsFetchSS = true; } else if (saplingSpendFile.length() != SAPLING_SPEND_SIZE) { needsFetchSS = true; } /* * We skip proving key verification every start - this is impractical. * If the proving key exists and is the correct size, then it should be OK. else { parent.setProgressText("Verifying proving key..."); needsFetch = !checkSHA256(provingKeyFile,parent); }*/ if (!needsFetch && !needsFetchSG && !needsFetchSS) { return; } JOptionPane.showMessageDialog( parent, langUtil.getString("proving.key.fetcher.option.pane.verify.message")); parent.setProgressText(langUtil.getString("proving.key.fetcher.option.pane.verify.progress.text")); if (needsFetch) { provingKeyFile.delete(); OutputStream os = new BufferedOutputStream(new FileOutputStream(provingKeyFile)); URL keyURL = new URL(pathURL); URLConnection urlc = keyURL.openConnection(); urlc.setRequestProperty("User-Agent", "Wget/1.17.1 (linux-gnu)"); try { is = urlc.getInputStream(); ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, langUtil.getString("proving.key.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(PROVING_KEY_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); copy(pmis,os); os.close(); } finally { try { if (is != null) is.close(); } catch (IOException ignore){} } parent.setProgressText(langUtil.getString("proving.key.fetcher.option.pane.verify.key.text")); if (!checkSHA256(provingKeyFile, parent)) { JOptionPane.showMessageDialog(parent, langUtil.getString("proving.key.fetcher.option.pane.verify.key.failed.text")); System.exit(-4); } } if (needsFetchSG) { sproutGrothFile.delete(); OutputStream os = new BufferedOutputStream(new FileOutputStream(sproutGrothFile)); URL keyURL = new URL(pathURLSG); URLConnection urlc = keyURL.openConnection(); urlc.setRequestProperty("User-Agent", "Wget/1.17.1 (linux-gnu)"); try { is = urlc.getInputStream(); ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, langUtil.getString("sprout.groth.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(SPROUT_GROTH_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); copy(pmis,os); os.close(); } finally { try { if (is != null) is.close(); } catch (IOException ignore){} } parent.setProgressText(langUtil.getString("sprout.groth.fetcher.option.pane.verify.key.text")); if (!checkSHA256SG(sproutGrothFile, parent)) { JOptionPane.showMessageDialog(parent, langUtil.getString("sapsproutling.groth.fetcher.option.pane.verify.key.failed.text")); System.exit(-4); } } if (needsFetchSS) { saplingSpendFile.delete(); OutputStream os = new BufferedOutputStream(new FileOutputStream(saplingSpendFile)); URL keyURL = new URL(pathURLSS); URLConnection urlc = keyURL.openConnection(); urlc.setRequestProperty("User-Agent", "Wget/1.17.1 (linux-gnu)"); try { is = urlc.getInputStream(); ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, langUtil.getString("sapling.spend.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(SAPLING_SPEND_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); copy(pmis,os); os.close(); } finally { try { if (is != null) is.close(); } catch (IOException ignore){} } parent.setProgressText(langUtil.getString("sapling.spend.fetcher.option.pane.verify.key.text")); if (!checkSHA256SS(saplingSpendFile, parent)) { JOptionPane.showMessageDialog(parent, langUtil.getString("sapling.spend.fetcher.option.pane.verify.key.failed.text")); System.exit(-4); } } } private static void copy(InputStream is, OutputStream os) throws IOException { byte[] buf = new byte[0x1 << 13]; int read; while ((read = is.read(buf)) >- 0) { os.write(buf,0,read); } os.flush(); } private static boolean checkSHA256(File provingKey, Component parent) throws IOException { MessageDigest sha256; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException impossible) { throw new IOException(impossible); } try (InputStream is = new BufferedInputStream(new FileInputStream(provingKey))) { ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, LanguageUtil.instance().getString("proving.key.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(PROVING_KEY_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); DigestInputStream dis = new DigestInputStream(pmis, sha256); byte [] temp = new byte[0x1 << 13]; while(dis.read(temp) >= 0); byte [] digest = sha256.digest(); return SHA256.equalsIgnoreCase(Util.bytesToHex(digest)); } } private static boolean checkSHA256SG(File sproutGroth, Component parent) throws IOException { MessageDigest sha256; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException impossible) { throw new IOException(impossible); } try (InputStream is = new BufferedInputStream(new FileInputStream(sproutGroth))) { ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, LanguageUtil.instance().getString("sprout.groth.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(SPROUT_GROTH_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); DigestInputStream dis = new DigestInputStream(pmis, sha256); byte [] temp = new byte[0x1 << 13]; while(dis.read(temp) >= 0); byte [] digest = sha256.digest(); return SHA256SG.equalsIgnoreCase(Util.bytesToHex(digest)); } } private static boolean checkSHA256SS(File saplingSpend, Component parent) throws IOException { MessageDigest sha256; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException impossible) { throw new IOException(impossible); } try (InputStream is = new BufferedInputStream(new FileInputStream(saplingSpend))) { ProgressMonitorInputStream pmis = new ProgressMonitorInputStream(parent, LanguageUtil.instance().getString("sapling.spend.fetcher.option.pane.verify.progress.monitor.text"), is); pmis.getProgressMonitor().setMaximum(SAPLING_SPEND_SIZE); pmis.getProgressMonitor().setMillisToPopup(10); DigestInputStream dis = new DigestInputStream(pmis, sha256); byte [] temp = new byte[0x1 << 13]; while(dis.read(temp) >= 0); byte [] digest = sha256.digest(); return SHA256SS.equalsIgnoreCase(Util.bytesToHex(digest)); } } }