PageRenderTime 46ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/wallet/src/de/schildbach/wallet/util/WalletUtils.java

https://code.google.com/
Java | 434 lines | 357 code | 55 blank | 22 comment | 39 complexity | 8e244ea791827e6f786deb58d8163ecb MD5 | raw file
Possible License(s): Apache-2.0, GPL-3.0
  1. /*
  2. * Copyright 2011-2014 the original author or authors.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. package de.schildbach.wallet.util;
  18. import java.io.BufferedReader;
  19. import java.io.ByteArrayInputStream;
  20. import java.io.ByteArrayOutputStream;
  21. import java.io.File;
  22. import java.io.FileFilter;
  23. import java.io.FileInputStream;
  24. import java.io.IOException;
  25. import java.io.InputStream;
  26. import java.io.InputStreamReader;
  27. import java.io.Writer;
  28. import java.math.BigInteger;
  29. import java.text.DateFormat;
  30. import java.text.ParseException;
  31. import java.util.Date;
  32. import java.util.LinkedList;
  33. import java.util.List;
  34. import java.util.regex.Matcher;
  35. import java.util.regex.Pattern;
  36. import javax.annotation.CheckForNull;
  37. import javax.annotation.Nonnull;
  38. import javax.annotation.Nullable;
  39. import android.graphics.Typeface;
  40. import android.text.Editable;
  41. import android.text.Spannable;
  42. import android.text.SpannableStringBuilder;
  43. import android.text.format.DateUtils;
  44. import android.text.style.RelativeSizeSpan;
  45. import android.text.style.StyleSpan;
  46. import android.text.style.TypefaceSpan;
  47. import com.google.bitcoin.core.Address;
  48. import com.google.bitcoin.core.AddressFormatException;
  49. import com.google.bitcoin.core.DumpedPrivateKey;
  50. import com.google.bitcoin.core.ECKey;
  51. import com.google.bitcoin.core.ScriptException;
  52. import com.google.bitcoin.core.Sha256Hash;
  53. import com.google.bitcoin.core.Transaction;
  54. import com.google.bitcoin.core.TransactionInput;
  55. import com.google.bitcoin.core.TransactionOutput;
  56. import com.google.bitcoin.core.Wallet;
  57. import com.google.bitcoin.script.Script;
  58. import com.google.bitcoin.store.UnreadableWalletException;
  59. import com.google.bitcoin.store.WalletProtobufSerializer;
  60. import com.google.common.base.Charsets;
  61. import de.schildbach.wallet.Constants;
  62. /**
  63. * @author Andreas Schildbach
  64. */
  65. public class WalletUtils
  66. {
  67. public static Editable formatAddress(@Nonnull final Address address, final int groupSize, final int lineSize)
  68. {
  69. return formatHash(address.toString(), groupSize, lineSize);
  70. }
  71. public static Editable formatAddress(@Nullable final String prefix, @Nonnull final Address address, final int groupSize, final int lineSize)
  72. {
  73. return formatHash(prefix, address.toString(), groupSize, lineSize, Constants.CHAR_THIN_SPACE);
  74. }
  75. public static Editable formatHash(@Nonnull final String address, final int groupSize, final int lineSize)
  76. {
  77. return formatHash(null, address, groupSize, lineSize, Constants.CHAR_THIN_SPACE);
  78. }
  79. public static long longHash(@Nonnull final Sha256Hash hash)
  80. {
  81. final byte[] bytes = hash.getBytes();
  82. return (bytes[31] & 0xFFl) | ((bytes[30] & 0xFFl) << 8) | ((bytes[29] & 0xFFl) << 16) | ((bytes[28] & 0xFFl) << 24)
  83. | ((bytes[27] & 0xFFl) << 32) | ((bytes[26] & 0xFFl) << 40) | ((bytes[25] & 0xFFl) << 48) | ((bytes[23] & 0xFFl) << 56);
  84. }
  85. public static Editable formatHash(@Nullable final String prefix, @Nonnull final String address, final int groupSize, final int lineSize,
  86. final char groupSeparator)
  87. {
  88. final SpannableStringBuilder builder = prefix != null ? new SpannableStringBuilder(prefix) : new SpannableStringBuilder();
  89. final int len = address.length();
  90. for (int i = 0; i < len; i += groupSize)
  91. {
  92. final int end = i + groupSize;
  93. final String part = address.substring(i, end < len ? end : len);
  94. builder.append(part);
  95. builder.setSpan(new TypefaceSpan("monospace"), builder.length() - part.length(), builder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  96. if (end < len)
  97. {
  98. final boolean endOfLine = lineSize > 0 && end % lineSize == 0;
  99. builder.append(endOfLine ? '\n' : groupSeparator);
  100. }
  101. }
  102. return builder;
  103. }
  104. private static final Pattern P_SIGNIFICANT = Pattern.compile("^([-+]" + Constants.CHAR_THIN_SPACE + ")?\\d*(\\.\\d{0,2})?");
  105. private static final Object SIGNIFICANT_SPAN = new StyleSpan(Typeface.BOLD);
  106. public static final RelativeSizeSpan SMALLER_SPAN = new RelativeSizeSpan(0.85f);
  107. public static void formatSignificant(@Nonnull final Spannable spannable, @Nullable final RelativeSizeSpan insignificantRelativeSizeSpan)
  108. {
  109. spannable.removeSpan(SIGNIFICANT_SPAN);
  110. if (insignificantRelativeSizeSpan != null)
  111. spannable.removeSpan(insignificantRelativeSizeSpan);
  112. final Matcher m = P_SIGNIFICANT.matcher(spannable);
  113. if (m.find())
  114. {
  115. final int pivot = m.group().length();
  116. if (pivot > 0)
  117. spannable.setSpan(SIGNIFICANT_SPAN, 0, pivot, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  118. if (spannable.length() > pivot && insignificantRelativeSizeSpan != null)
  119. spannable.setSpan(insignificantRelativeSizeSpan, pivot, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
  120. }
  121. }
  122. public static BigInteger localValue(@Nonnull final BigInteger btcValue, @Nonnull final BigInteger rate)
  123. {
  124. return btcValue.multiply(rate).divide(GenericUtils.ONE_BTC);
  125. }
  126. public static BigInteger btcValue(@Nonnull final BigInteger localValue, @Nonnull final BigInteger rate)
  127. {
  128. return localValue.multiply(GenericUtils.ONE_BTC).divide(rate);
  129. }
  130. @CheckForNull
  131. public static Address getFirstFromAddress(@Nonnull final Transaction tx)
  132. {
  133. if (tx.isCoinBase())
  134. return null;
  135. try
  136. {
  137. for (final TransactionInput input : tx.getInputs())
  138. {
  139. return input.getFromAddress();
  140. }
  141. throw new IllegalStateException();
  142. }
  143. catch (final ScriptException x)
  144. {
  145. // this will happen on inputs connected to coinbase transactions
  146. return null;
  147. }
  148. }
  149. @CheckForNull
  150. public static Address getFirstToAddress(@Nonnull final Transaction tx)
  151. {
  152. try
  153. {
  154. for (final TransactionOutput output : tx.getOutputs())
  155. {
  156. return output.getScriptPubKey().getToAddress(Constants.NETWORK_PARAMETERS);
  157. }
  158. throw new IllegalStateException();
  159. }
  160. catch (final ScriptException x)
  161. {
  162. return null;
  163. }
  164. }
  165. public static boolean isInternal(@Nonnull final Transaction tx)
  166. {
  167. if (tx.isCoinBase())
  168. return false;
  169. final List<TransactionOutput> outputs = tx.getOutputs();
  170. if (outputs.size() != 1)
  171. return false;
  172. try
  173. {
  174. final TransactionOutput output = outputs.get(0);
  175. final Script scriptPubKey = output.getScriptPubKey();
  176. if (!scriptPubKey.isSentToRawPubKey())
  177. return false;
  178. return true;
  179. }
  180. catch (final ScriptException x)
  181. {
  182. return false;
  183. }
  184. }
  185. public static Wallet restoreWalletFromProtobufOrBase58(final InputStream is) throws IOException
  186. {
  187. is.mark((int) Constants.BACKUP_MAX_CHARS);
  188. try
  189. {
  190. return restoreWalletFromProtobuf(is);
  191. }
  192. catch (final IOException x)
  193. {
  194. try
  195. {
  196. is.reset();
  197. return restorePrivateKeysFromBase58(is);
  198. }
  199. catch (final IOException x2)
  200. {
  201. throw new IOException("cannot read protobuf (" + x.getMessage() + ") or base58 (" + x2.getMessage() + ")", x);
  202. }
  203. }
  204. }
  205. public static Wallet restoreWalletFromProtobuf(final InputStream is) throws IOException
  206. {
  207. try
  208. {
  209. final Wallet wallet = new WalletProtobufSerializer().readWallet(is);
  210. if (!wallet.getParams().equals(Constants.NETWORK_PARAMETERS))
  211. throw new IOException("bad wallet network parameters: " + wallet.getParams().getId());
  212. return wallet;
  213. }
  214. catch (final UnreadableWalletException x)
  215. {
  216. throw new IOException("unreadable wallet", x);
  217. }
  218. }
  219. public static Wallet restorePrivateKeysFromBase58(final InputStream is) throws IOException
  220. {
  221. final BufferedReader keyReader = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8));
  222. final Wallet wallet = new Wallet(Constants.NETWORK_PARAMETERS);
  223. wallet.addKeys(WalletUtils.readKeys(keyReader));
  224. return wallet;
  225. }
  226. public static void writeKeys(@Nonnull final Writer out, @Nonnull final List<ECKey> keys) throws IOException
  227. {
  228. final DateFormat format = Iso8601Format.newDateTimeFormatT();
  229. out.write("# KEEP YOUR PRIVATE KEYS SAFE! Anyone who can read this can spend your Bitcoins.\n");
  230. for (final ECKey key : keys)
  231. {
  232. out.write(key.getPrivateKeyEncoded(Constants.NETWORK_PARAMETERS).toString());
  233. if (key.getCreationTimeSeconds() != 0)
  234. {
  235. out.write(' ');
  236. out.write(format.format(new Date(key.getCreationTimeSeconds() * DateUtils.SECOND_IN_MILLIS)));
  237. }
  238. out.write('\n');
  239. }
  240. }
  241. public static List<ECKey> readKeys(@Nonnull final BufferedReader in) throws IOException
  242. {
  243. try
  244. {
  245. final DateFormat format = Iso8601Format.newDateTimeFormatT();
  246. final List<ECKey> keys = new LinkedList<ECKey>();
  247. long charCount = 0;
  248. while (true)
  249. {
  250. final String line = in.readLine();
  251. if (line == null)
  252. break; // eof
  253. charCount += line.length();
  254. if (charCount > Constants.BACKUP_MAX_CHARS)
  255. throw new IOException("read more than the limit of " + Constants.BACKUP_MAX_CHARS + " characters");
  256. if (line.trim().isEmpty() || line.charAt(0) == '#')
  257. continue; // skip comment
  258. final String[] parts = line.split(" ");
  259. final ECKey key = new DumpedPrivateKey(Constants.NETWORK_PARAMETERS, parts[0]).getKey();
  260. key.setCreationTimeSeconds(parts.length >= 2 ? format.parse(parts[1]).getTime() / DateUtils.SECOND_IN_MILLIS : 0);
  261. keys.add(key);
  262. }
  263. return keys;
  264. }
  265. catch (final AddressFormatException x)
  266. {
  267. throw new IOException("cannot read keys", x);
  268. }
  269. catch (final ParseException x)
  270. {
  271. throw new IOException("cannot read keys", x);
  272. }
  273. }
  274. public static final FileFilter KEYS_FILE_FILTER = new FileFilter()
  275. {
  276. @Override
  277. public boolean accept(final File file)
  278. {
  279. BufferedReader reader = null;
  280. try
  281. {
  282. reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), Charsets.UTF_8));
  283. WalletUtils.readKeys(reader);
  284. return true;
  285. }
  286. catch (final IOException x)
  287. {
  288. return false;
  289. }
  290. finally
  291. {
  292. if (reader != null)
  293. {
  294. try
  295. {
  296. reader.close();
  297. }
  298. catch (final IOException x)
  299. {
  300. // swallow
  301. }
  302. }
  303. }
  304. }
  305. };
  306. public static final FileFilter BACKUP_FILE_FILTER = new FileFilter()
  307. {
  308. @Override
  309. public boolean accept(final File file)
  310. {
  311. InputStream is = null;
  312. try
  313. {
  314. is = new FileInputStream(file);
  315. return WalletProtobufSerializer.isWallet(is);
  316. }
  317. catch (final IOException x)
  318. {
  319. return false;
  320. }
  321. finally
  322. {
  323. if (is != null)
  324. {
  325. try
  326. {
  327. is.close();
  328. }
  329. catch (final IOException x)
  330. {
  331. // swallow
  332. }
  333. }
  334. }
  335. }
  336. };
  337. @CheckForNull
  338. public static ECKey pickOldestKey(@Nonnull final Wallet wallet)
  339. {
  340. ECKey oldestKey = null;
  341. for (final ECKey key : wallet.getKeys())
  342. if (!wallet.isKeyRotating(key))
  343. if (oldestKey == null || key.getCreationTimeSeconds() < oldestKey.getCreationTimeSeconds())
  344. oldestKey = key;
  345. return oldestKey;
  346. }
  347. public static byte[] walletToByteArray(@Nonnull final Wallet wallet)
  348. {
  349. try
  350. {
  351. final ByteArrayOutputStream os = new ByteArrayOutputStream();
  352. new WalletProtobufSerializer().writeWallet(wallet, os);
  353. os.close();
  354. return os.toByteArray();
  355. }
  356. catch (final IOException x)
  357. {
  358. throw new RuntimeException(x);
  359. }
  360. }
  361. public static Wallet walletFromByteArray(@Nonnull final byte[] walletBytes)
  362. {
  363. try
  364. {
  365. final ByteArrayInputStream is = new ByteArrayInputStream(walletBytes);
  366. final Wallet wallet = new WalletProtobufSerializer().readWallet(is);
  367. is.close();
  368. return wallet;
  369. }
  370. catch (final UnreadableWalletException x)
  371. {
  372. throw new RuntimeException(x);
  373. }
  374. catch (final IOException x)
  375. {
  376. throw new RuntimeException(x);
  377. }
  378. }
  379. }