PageRenderTime 70ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/info/blockchain/wallet/payload/PayloadManager.java

https://gitlab.com/admin-github-cloud/My-Wallet-V3-jar
Java | 885 lines | 629 code | 152 blank | 104 comment | 116 complexity | 5b7a919ffd9774cd93da4e57f5c303f6 MD5 | raw file
  1. package info.blockchain.wallet.payload;
  2. import info.blockchain.bip44.Address;
  3. import info.blockchain.bip44.Wallet;
  4. import info.blockchain.wallet.crypto.AESUtil;
  5. import info.blockchain.wallet.multiaddr.MultiAddrFactory;
  6. import info.blockchain.wallet.util.*;
  7. import org.apache.commons.codec.DecoderException;
  8. import org.apache.commons.lang3.StringUtils;
  9. import org.bitcoinj.core.AddressFormatException;
  10. import org.bitcoinj.core.Base58;
  11. import org.bitcoinj.core.ECKey;
  12. import org.bitcoinj.crypto.MnemonicException;
  13. import org.bitcoinj.params.MainNetParams;
  14. import org.json.JSONException;
  15. import org.json.JSONObject;
  16. import org.spongycastle.util.encoders.Hex;
  17. import javax.annotation.Nullable;
  18. import java.io.IOException;
  19. import java.io.UnsupportedEncodingException;
  20. import java.net.URLEncoder;
  21. import java.security.MessageDigest;
  22. import java.security.NoSuchAlgorithmException;
  23. import java.security.SecureRandom;
  24. import java.util.ArrayList;
  25. import java.util.List;
  26. /**
  27. *
  28. * PayloadManager.java : singleton class for reading/writing/parsing Blockchain HD JSON payload
  29. *
  30. */
  31. public class PayloadManager {
  32. public static final double SUPPORTED_ENCRYPTION_VERSION = 3.0;
  33. public static final long NORMAL_ADDRESS = 0L;
  34. public static final long ARCHIVED_ADDRESS = 2L;
  35. public static final int RECEIVE_CHAIN = 0;
  36. public static final int CHANGE_CHAIN = 0;
  37. private static PayloadManager instance = null;
  38. // active payload:
  39. private static Payload payload = null;
  40. // cached payload, compare to this payload to determine if changes have been made. Used to avoid needless remote saves to server
  41. private static String cached_payload = null;
  42. private static final int WalletDefaultPbkdf2Iterations = 5000;
  43. public static int WalletPbkdf2Iterations = WalletDefaultPbkdf2Iterations;
  44. private static CharSequenceX strTempPassword = null;
  45. private static String strCheckSum = null;
  46. private static boolean isNew = false;
  47. private static boolean syncPubKeys = true;
  48. private static String email = null;
  49. private static double version = 2.0;
  50. private static HDPayloadBridge hdPayloadBridge;
  51. private static Wallet wallet;
  52. private static Wallet watchOnlyWallet;
  53. private PayloadManager() {
  54. ;
  55. }
  56. /**
  57. * Return instance for a payload factory.
  58. *
  59. * @return PayloadManager
  60. */
  61. public static PayloadManager getInstance() {
  62. if (instance == null) {
  63. instance = new PayloadManager();
  64. payload = new Payload();
  65. cached_payload = "";
  66. hdPayloadBridge = new HDPayloadBridge();
  67. }
  68. return instance;
  69. }
  70. /**
  71. * Reset PayloadManager to null instance.
  72. */
  73. public void wipe() {
  74. instance = null;
  75. }
  76. public interface InitiatePayloadListener {
  77. void onInitSuccess();
  78. void onInitPairFail();
  79. void onInitCreateFail(String error);
  80. }
  81. /*
  82. Initiate payload after pairing or after PIN entered
  83. */
  84. public void initiatePayload(String sharedKey, String guid, CharSequenceX password, InitiatePayloadListener listener) throws Exception {
  85. payload = getPayloadFromServer(guid, sharedKey, password);
  86. if (payload == null || payload.stepNumber != 9) {
  87. String error = "";
  88. if (payload != null) {
  89. error = error + " Failed at step: " + payload.stepNumber;
  90. if (payload.lastErrorMessage != null) {
  91. error = error + " with message: " + payload.lastErrorMessage;
  92. }
  93. }
  94. if(listener != null)listener.onInitCreateFail(error);
  95. }
  96. if (payload.getJSON() == null && listener != null) {
  97. listener.onInitPairFail();
  98. }
  99. syncWallet();
  100. if(listener != null){
  101. listener.onInitSuccess();
  102. }
  103. }
  104. private void syncWallet() throws Exception{
  105. if (payload.getHdWallet() != null) {
  106. if (payload.isDoubleEncrypted()) {
  107. watchOnlyWallet = hdPayloadBridge.getHDWatchOnlyWalletFromXpubs(getXPUBs(true));
  108. } else {
  109. wallet = hdPayloadBridge.getHDWalletFromPayload(payload);
  110. }
  111. }else{
  112. //V2 wallet - no need to keep in sync with bp44 wallet
  113. }
  114. }
  115. /**
  116. * Get temporary password for user. Read password from here rather than reprompting user.
  117. *
  118. * @return CharSequenceX
  119. */
  120. public CharSequenceX getTempPassword() {
  121. return strTempPassword;
  122. }
  123. /**
  124. * Set temporary password for user once it has been validated. Read password from here rather than reprompting user.
  125. *
  126. * @param temp_password Validated user password
  127. */
  128. public void setTempPassword(CharSequenceX temp_password) {
  129. strTempPassword = temp_password;
  130. clearCachedPayload();
  131. }
  132. /**
  133. * Get checksum for this payload.
  134. *
  135. * @return String
  136. */
  137. public String getCheckSum() {
  138. return strCheckSum;
  139. }
  140. /**
  141. * Set checksum for this payload.
  142. *
  143. * @param checksum Checksum to be set for this payload
  144. */
  145. public void setCheckSum(String checksum) {
  146. this.strCheckSum = checksum;
  147. }
  148. /**
  149. * Check if this payload is for a new Blockchain account.
  150. *
  151. * @return boolean
  152. */
  153. public boolean isNew() {
  154. return isNew;
  155. }
  156. /**
  157. * Set if this payload is for a new Blockchain account.
  158. *
  159. * @param isNew
  160. */
  161. public void setNew(boolean isNew) {
  162. this.isNew = isNew;
  163. }
  164. /**
  165. * Remote get(). Get refreshed payload from server.
  166. *
  167. * @param guid User's wallet 'guid'
  168. * @param sharedKey User's sharedKey value
  169. * @param password User password
  170. * @return Payload
  171. */
  172. private Payload getPayloadFromServer(String guid, String sharedKey, CharSequenceX password) {
  173. Payload payload = null;
  174. String checksum = null;
  175. try {
  176. String response = WebUtil.getInstance().postURL(WebUtil.PAYLOAD_URL, "method=wallet.aes.json&guid=" + guid + "&sharedKey=" + sharedKey + "&format=json" + "&api_code=" + WebUtil.API_CODE);
  177. JSONObject jsonObject = new JSONObject(response);
  178. if (jsonObject.has("payload_checksum")) {
  179. checksum = jsonObject.get("payload_checksum").toString();
  180. }
  181. if (jsonObject.has("payload")) {
  182. String encrypted_payload = null;
  183. JSONObject _jsonObject = null;
  184. try {
  185. _jsonObject = new JSONObject((String) jsonObject.get("payload"));
  186. } catch (Exception e) {
  187. _jsonObject = null;
  188. }
  189. if (_jsonObject != null && _jsonObject.has("payload")) {
  190. if (_jsonObject.has("pbkdf2_iterations")) {
  191. WalletPbkdf2Iterations = Integer.valueOf(_jsonObject.get("pbkdf2_iterations").toString());
  192. }
  193. if (_jsonObject.has("version")) {
  194. version = Double.valueOf(_jsonObject.get("version").toString());
  195. }
  196. encrypted_payload = (String) _jsonObject.get("payload");
  197. } else {
  198. if (jsonObject.has("pbkdf2_iterations")) {
  199. WalletPbkdf2Iterations = Integer.valueOf(jsonObject.get("pbkdf2_iterations").toString());
  200. }
  201. if (jsonObject.has("version")) {
  202. version = Double.valueOf(jsonObject.get("version").toString());
  203. }
  204. encrypted_payload = (String) jsonObject.get("payload");
  205. }
  206. String decrypted = null;
  207. try {
  208. decrypted = AESUtil.decrypt(encrypted_payload, password, WalletPbkdf2Iterations);
  209. } catch (Exception e) {
  210. payload.lastErrorMessage = e.getMessage();
  211. e.printStackTrace();
  212. return null;
  213. }
  214. if (decrypted == null) {
  215. try {
  216. // v1 wallet fixed PBKDF2 iterations at 10
  217. decrypted = AESUtil.decrypt(encrypted_payload, password, 10);
  218. } catch (Exception e) {
  219. payload.lastErrorMessage = e.getMessage();
  220. e.printStackTrace();
  221. return null;
  222. }
  223. if (decrypted == null) {
  224. payload.lastErrorMessage = "Empty after decrypt";
  225. return null;
  226. }
  227. }
  228. payload = new Payload(decrypted);
  229. if (payload.getJSON() == null) {
  230. payload.lastErrorMessage = "Can't parse JSON";
  231. return null;
  232. }
  233. // Default to wallet pbkdf2 iterations in case the double encryption pbkdf2 iterations is not set in wallet.json > options
  234. payload.setDoubleEncryptionPbkdf2Iterations(WalletPbkdf2Iterations);
  235. try {
  236. payload.parseJSON();
  237. } catch (JSONException je) {
  238. payload.lastErrorMessage = je.getMessage();
  239. je.printStackTrace();
  240. return null;
  241. }
  242. } else {
  243. // Log.i("PayloadManager", "jsonObject has no payload");
  244. return null;
  245. }
  246. } catch (JSONException e) {
  247. payload.lastErrorMessage = e.getMessage();
  248. e.printStackTrace();
  249. return null;
  250. } catch (Exception e) {
  251. payload.lastErrorMessage = e.getMessage();
  252. e.printStackTrace();
  253. return null;
  254. }
  255. if (StringUtils.isNotEmpty(checksum)) {
  256. strCheckSum = checksum;
  257. }
  258. return payload;
  259. }
  260. /**
  261. * Local get(). Returns current payload from the client.
  262. *
  263. * @return Payload
  264. */
  265. public Payload getPayload() {
  266. return payload;
  267. }
  268. /**
  269. * Local set(). Sets current payload on the client.
  270. *
  271. * @param p Payload to be assigned
  272. */
  273. public void setPayload(Payload p) {
  274. payload = p;
  275. }
  276. /**
  277. * Remote save of current client payload to server. Will not save if no change as compared to cached payload.
  278. *
  279. * @return boolean
  280. */
  281. public boolean savePayloadToServer() {
  282. if (payload == null) return false;
  283. String strOldCheckSum = strCheckSum;
  284. String payloadCleartext = null;
  285. StringBuilder args = new StringBuilder();
  286. try {
  287. if (cached_payload != null && cached_payload.equals(payload.dumpJSON().toString())) {
  288. return true;
  289. }
  290. payloadCleartext = payload.dumpJSON().toString();
  291. String payloadEncrypted = AESUtil.encrypt(payloadCleartext, new CharSequenceX(strTempPassword), WalletPbkdf2Iterations);
  292. JSONObject rootObj = new JSONObject();
  293. rootObj.put("version", payload.isUpgraded() ? 3.0 : 2.0);
  294. rootObj.put("pbkdf2_iterations", WalletPbkdf2Iterations);
  295. rootObj.put("payload", payloadEncrypted);
  296. strCheckSum = new String(Hex.encode(MessageDigest.getInstance("SHA-256").digest(rootObj.toString().getBytes("UTF-8"))));
  297. String method = isNew ? "insert" : "update";
  298. String urlEncodedPayload = URLEncoder.encode(rootObj.toString());
  299. args.append("guid=");
  300. args.append(URLEncoder.encode(payload.getGuid(), "utf-8"));
  301. args.append("&sharedKey=");
  302. args.append(URLEncoder.encode(payload.getSharedKey(), "utf-8"));
  303. args.append("&payload=");
  304. args.append(urlEncodedPayload);
  305. args.append("&method=");
  306. args.append(method);
  307. args.append("&length=");
  308. args.append(rootObj.toString().length());
  309. args.append("&checksum=");
  310. args.append(URLEncoder.encode(strCheckSum, "utf-8"));
  311. } catch (NoSuchAlgorithmException nsae) {
  312. nsae.printStackTrace();
  313. return false;
  314. } catch (UnsupportedEncodingException uee) {
  315. uee.printStackTrace();
  316. return false;
  317. } catch (JSONException je) {
  318. je.printStackTrace();
  319. return false;
  320. }
  321. if (syncPubKeys) {
  322. args.append("&active=");
  323. String[] legacyAddrs = null;
  324. List<LegacyAddress> legacyAddresses = payload.getLegacyAddresses();
  325. List<String> addrs = new ArrayList<String>();
  326. for (LegacyAddress addr : legacyAddresses) {
  327. if (addr.getTag() == 0L) {
  328. addrs.add(addr.getAddress());
  329. }
  330. }
  331. args.append(StringUtils.join(addrs.toArray(new String[addrs.size()]), "|"));
  332. }
  333. if (email != null && email.length() > 0) {
  334. try {
  335. args.append("&email=");
  336. args.append(URLEncoder.encode(email, "utf-8"));
  337. } catch (UnsupportedEncodingException e) {
  338. e.printStackTrace();
  339. }
  340. }
  341. args.append("&device=");
  342. args.append("android");
  343. if (strOldCheckSum != null && strOldCheckSum.length() > 0) {
  344. args.append("&old_checksum=");
  345. args.append(strOldCheckSum);
  346. }
  347. args.append("&api_code=" + WebUtil.API_CODE);
  348. try {
  349. String response = WebUtil.getInstance().postURL(WebUtil.PAYLOAD_URL, args.toString());
  350. isNew = false;
  351. if (response.contains("Wallet successfully synced")) {
  352. cachePayload();
  353. return true;
  354. }
  355. } catch (Exception e) {
  356. e.printStackTrace();
  357. return false;
  358. }
  359. return true;
  360. }
  361. /**
  362. * Write to current client payload to cache.
  363. */
  364. public void cachePayload() {
  365. try {
  366. cached_payload = payload.dumpJSON().toString();
  367. } catch (JSONException je) {
  368. je.printStackTrace();
  369. }
  370. }
  371. private void clearCachedPayload() {
  372. cached_payload = null;
  373. }
  374. public String getEmail() {
  375. return email;
  376. }
  377. public void setEmail(String email) {
  378. PayloadManager.email = email;
  379. }
  380. public double getVersion() {
  381. return version;
  382. }
  383. public boolean validateSecondPassword(String secondPassword){
  384. return DoubleEncryptionFactory.getInstance().validateSecondPassword(
  385. payload.getDoublePasswordHash(),
  386. payload.getSharedKey(),
  387. new CharSequenceX(secondPassword),
  388. payload.getDoubleEncryptionPbkdf2Iterations());
  389. }
  390. public boolean decryptDoubleEncryptedWallet(String secondPassword) {
  391. if (validateSecondPassword(secondPassword)) {
  392. String encrypted_hex = payload.getHdWallet().getSeedHex();
  393. String decrypted_hex = DoubleEncryptionFactory.getInstance().decrypt(
  394. encrypted_hex,
  395. payload.getSharedKey(),
  396. secondPassword,
  397. payload.getDoubleEncryptionPbkdf2Iterations());
  398. try {
  399. watchOnlyWallet = hdPayloadBridge.decryptWatchOnlyWallet(payload, decrypted_hex);
  400. } catch (Exception e) {
  401. e.printStackTrace();
  402. return false;
  403. }
  404. return true;
  405. } else {
  406. return false;
  407. }
  408. }
  409. public String[] getMnemonicForDoubleEncryptedWallet(String secondPassword) {
  410. if (validateSecondPassword(secondPassword)) {
  411. // Decrypt seedHex (which is double encrypted in this case)
  412. String decrypted_hex = DoubleEncryptionFactory.getInstance().decrypt(
  413. payload.getHdWallet().getSeedHex(),
  414. payload.getSharedKey(),
  415. secondPassword,
  416. payload.getDoubleEncryptionPbkdf2Iterations());
  417. String mnemonic = null;
  418. try {
  419. watchOnlyWallet = hdPayloadBridge.decryptWatchOnlyWallet(payload, decrypted_hex);
  420. mnemonic = watchOnlyWallet.getMnemonic();
  421. } catch (Exception e) {
  422. e.printStackTrace();
  423. } finally {
  424. if (mnemonic != null && mnemonic.length() > 0) {
  425. return mnemonic.split("\\s+");
  426. } else {
  427. return null;
  428. }
  429. }
  430. }else{
  431. return null;
  432. }
  433. }
  434. public Payload createHDWallet(String payloadPassword, String defaultAccountName) throws Exception {
  435. setTempPassword(new CharSequenceX(payloadPassword));
  436. HDPayloadBridge.HDWalletPayloadPair pair = hdPayloadBridge.createHDWallet(defaultAccountName);
  437. wallet = pair.wallet;
  438. payload = pair.payload;
  439. setNew(true);
  440. if(!savePayloadToServer()){
  441. //if save failed don't return payload
  442. payload = null;
  443. }
  444. return payload;
  445. }
  446. public Payload restoreHDWallet(String payloadPassword, String seed, String defaultAccountName) throws Exception {
  447. setTempPassword(new CharSequenceX(payloadPassword));
  448. HDPayloadBridge.HDWalletPayloadPair pair = hdPayloadBridge.restoreHDWallet(seed, defaultAccountName);
  449. wallet = pair.wallet;
  450. payload = pair.payload;
  451. setNew(true);
  452. if(!savePayloadToServer()){
  453. //if save failed don't return payload
  454. payload = null;
  455. }
  456. return payload;
  457. }
  458. public Payload restoreHDWallet(String payloadPassword, String seed, String defaultAccountName, String passphrase) throws Exception {
  459. setTempPassword(new CharSequenceX(payloadPassword));
  460. HDPayloadBridge.HDWalletPayloadPair pair = hdPayloadBridge.restoreHDWallet(seed, defaultAccountName, passphrase);
  461. wallet = pair.wallet;
  462. payload = pair.payload;
  463. setNew(true);
  464. if(!savePayloadToServer()){
  465. //if save failed don't return payload
  466. payload = null;
  467. }
  468. return payload;
  469. }
  470. public interface UpgradePayloadListener{
  471. void onDoubleEncryptionPasswordError();
  472. void onUpgradeSuccess();
  473. void onUpgradeFail();
  474. }
  475. /*
  476. When called from Android - First apply PRNGFixes
  477. */
  478. public void upgradeV2PayloadToV3(CharSequenceX secondPassword, boolean isNewlyCreated, String defaultAccountName, final UpgradePayloadListener listener) throws Exception {
  479. //Check if payload has 2nd password
  480. if (payload.isDoubleEncrypted()) {
  481. //Validate 2nd password
  482. if (StringUtils.isEmpty(secondPassword) || !validateSecondPassword(secondPassword.toString())) {
  483. listener.onDoubleEncryptionPasswordError();
  484. }
  485. }
  486. //Upgrade
  487. boolean isUpgradeSuccessful = hdPayloadBridge.upgradeV2PayloadToV3(payload, secondPassword, isNewlyCreated, defaultAccountName);
  488. if(isUpgradeSuccessful) {
  489. if (savePayloadToServer()) {
  490. syncWallet();
  491. updateBalancesAndTransactions();
  492. cachePayload();
  493. listener.onUpgradeSuccess();
  494. }else{
  495. listener.onUpgradeFail();//failed to save
  496. }
  497. }else{
  498. listener.onUpgradeFail();//failed to create
  499. }
  500. }
  501. public String getChangeAddress(int accountIndex) throws Exception {
  502. int changeIdx = payload.getHdWallet().getAccounts().get(accountIndex).getIdxChangeAddresses();
  503. if (!payload.isDoubleEncrypted()) {
  504. return wallet.getAccount(accountIndex).getChange().getAddressAt(changeIdx).getAddressString();
  505. } else {
  506. return watchOnlyWallet.getAccount(accountIndex).getChange().getAddressAt(changeIdx).getAddressString();
  507. }
  508. }
  509. public String getReceiveAddress(int accountIndex) {
  510. try {
  511. Address addr = null;
  512. int idx = payload.getHdWallet().getAccounts().get(accountIndex).getIdxReceiveAddresses();
  513. if (!payload.isDoubleEncrypted()) {
  514. addr = wallet.getAccount(accountIndex).getChain(RECEIVE_CHAIN).getAddressAt(idx);
  515. } else {
  516. addr = watchOnlyWallet.getAccount(accountIndex).getChain(RECEIVE_CHAIN).getAddressAt(idx);
  517. }
  518. ReceiveAddress receiveAddress = new ReceiveAddress(addr.getAddressString(), idx);
  519. return receiveAddress.getAddress();
  520. } catch (Exception e) {
  521. e.printStackTrace();
  522. return null;
  523. }
  524. }
  525. public ECKey getECKey(int accountIndex, String path) throws Exception {
  526. String[] s = path.split("/");
  527. Address hd_address = null;
  528. if (!payload.isDoubleEncrypted()) {
  529. hd_address = wallet.getAccount(accountIndex).getChain(Integer.parseInt(s[1])).getAddressAt(Integer.parseInt(s[2]));
  530. } else {
  531. hd_address = watchOnlyWallet.getAccount(accountIndex).getChain(Integer.parseInt(s[1])).getAddressAt(Integer.parseInt(s[2]));
  532. }
  533. return PrivateKeyFactory.getInstance().getKey(PrivateKeyFactory.WIF_COMPRESSED, hd_address.getPrivateKeyString());
  534. }
  535. public String getXpubFromAccountIndex(int accountIdx) {
  536. return payload.getHdWallet().getAccounts().get(accountIdx).getXpub();
  537. }
  538. public void updateBalancesAndTransactions() throws Exception {
  539. // TODO unify legacy and HD call to one API call
  540. // TODO getXpub must be called before getLegacy (unify should fix this)
  541. boolean isNotUpgraded = isNotUpgraded();
  542. // xPub balance
  543. if (!isNotUpgraded) {
  544. String[] xpubs = getXPUBs(false);
  545. if (xpubs.length > 0) {
  546. MultiAddrFactory.getInstance().refreshXPUBData(xpubs);
  547. }
  548. List<Account> accounts = payload.getHdWallet().getAccounts();
  549. for (Account a : accounts) {
  550. a.setIdxReceiveAddresses(MultiAddrFactory.getInstance().getHighestTxReceiveIdx(a.getXpub()) > a.getIdxReceiveAddresses() ?
  551. MultiAddrFactory.getInstance().getHighestTxReceiveIdx(a.getXpub()) : a.getIdxReceiveAddresses());
  552. a.setIdxChangeAddresses(MultiAddrFactory.getInstance().getHighestTxChangeIdx(a.getXpub()) > a.getIdxChangeAddresses() ?
  553. MultiAddrFactory.getInstance().getHighestTxChangeIdx(a.getXpub()) : a.getIdxChangeAddresses());
  554. }
  555. }
  556. // Balance for legacy addresses
  557. if (payload.getLegacyAddresses().size() > 0) {
  558. List<String> legacyAddresses = payload.getLegacyAddressStrings();
  559. String[] addresses = legacyAddresses.toArray(new String[legacyAddresses.size()]);
  560. MultiAddrFactory.getInstance().refreshLegacyAddressData(addresses, false);
  561. }
  562. }
  563. public boolean isNotUpgraded() {
  564. return payload != null && !payload.isUpgraded();
  565. }
  566. public String[] getXPUBs(boolean includeArchives) throws IOException, DecoderException, AddressFormatException, MnemonicException.MnemonicLengthException, MnemonicException.MnemonicChecksumException, MnemonicException.MnemonicWordException {
  567. ArrayList<String> xpubs = new ArrayList<String>();
  568. if (payload.getHdWallet() != null) {
  569. int nb_accounts = payload.getHdWallet().getAccounts().size();
  570. for (int i = 0; i < nb_accounts; i++) {
  571. boolean isArchived = payload.getHdWallet().getAccounts().get(i).isArchived();
  572. if (isArchived && !includeArchives) {
  573. ;
  574. } else {
  575. String s = payload.getHdWallet().getAccounts().get(i).getXpub();
  576. if (s != null && s.length() > 0) {
  577. xpubs.add(s);
  578. }
  579. }
  580. }
  581. }
  582. return xpubs.toArray(new String[xpubs.size()]);
  583. }
  584. public interface AccountAddListener {
  585. void onAccountAddSuccess(Account account);
  586. void onSecondPasswordFail();
  587. void onPayloadSaveFail();
  588. }
  589. public void addAccount(String accountLabel, @Nullable String secondPassword, AccountAddListener listener) throws Exception {
  590. //Add account
  591. String xpub = null;
  592. String xpriv = null;
  593. if(!payload.isDoubleEncrypted()) {
  594. wallet.addAccount();
  595. xpub = wallet.getAccounts().get(wallet.getAccounts().size() - 1).xpubstr();
  596. xpriv = wallet.getAccounts().get(wallet.getAccounts().size() - 1).xprvstr();
  597. }
  598. else {
  599. if(DoubleEncryptionFactory.getInstance().validateSecondPassword(
  600. payload.getDoublePasswordHash(),
  601. payload.getSharedKey(),
  602. new CharSequenceX(secondPassword),
  603. payload.getOptions().getIterations())){
  604. String decrypted_hex = DoubleEncryptionFactory.getInstance().decrypt(
  605. payload.getHdWallet().getSeedHex(),
  606. payload.getSharedKey(),
  607. secondPassword,
  608. payload.getDoubleEncryptionPbkdf2Iterations());
  609. //Need to decrypt watch-only wallet before adding new xpub
  610. watchOnlyWallet = hdPayloadBridge.decryptWatchOnlyWallet(payload, decrypted_hex);
  611. watchOnlyWallet.addAccount();
  612. xpub = watchOnlyWallet.getAccounts().get(watchOnlyWallet.getAccounts().size() - 1).xpubstr();
  613. xpriv = watchOnlyWallet.getAccounts().get(watchOnlyWallet.getAccounts().size() - 1).xprvstr();
  614. }else{
  615. listener.onSecondPasswordFail();
  616. return;
  617. }
  618. }
  619. //Initialize newly created xpub's tx list and balance
  620. List<Tx> txs = new ArrayList<Tx>();
  621. MultiAddrFactory.getInstance().getXpubTxs().put(xpub, txs);
  622. MultiAddrFactory.getInstance().getXpubAmounts().put(xpub, 0L);
  623. //Get account list from payload (not in sync with wallet from WalletFactory)
  624. List<Account> accounts = payload.getHdWallet().getAccounts();
  625. //Create new account (label, xpub, xpriv)
  626. Account account = new Account(accountLabel);
  627. account.setXpub(xpub);
  628. if(!payload.isDoubleEncrypted()) {
  629. account.setXpriv(xpriv);
  630. }
  631. else {
  632. String encrypted_xpriv = DoubleEncryptionFactory.getInstance().encrypt(
  633. xpriv,
  634. payload.getSharedKey(),
  635. secondPassword,
  636. payload.getDoubleEncryptionPbkdf2Iterations());
  637. account.setXpriv(encrypted_xpriv);
  638. }
  639. //Add new account to payload
  640. if(accounts.get(accounts.size() - 1) instanceof ImportedAccount) {
  641. accounts.add(accounts.size() - 1, account);
  642. }
  643. else {
  644. accounts.add(account);
  645. }
  646. payload.getHdWallet().setAccounts(accounts);
  647. //Save payload
  648. if (savePayloadToServer()) {
  649. if(listener != null)listener.onAccountAddSuccess(account);
  650. } else {
  651. if(listener != null)listener.onPayloadSaveFail();
  652. }
  653. }
  654. /*
  655. Generate V2 legacy address
  656. When called from Android - First apply PRNGFixes
  657. */
  658. public LegacyAddress generateLegacyAddress(String deviceName, String deviceVersion, String secondPassword){
  659. if(payload.isDoubleEncrypted() && !validateSecondPassword(secondPassword)){
  660. return null;//second password validation failed
  661. }
  662. ECKey ecKey = getRandomECKey();
  663. String encryptedKey = new String(Base58.encode(ecKey.getPrivKeyBytes()));
  664. if (payload.isDoubleEncrypted()) {
  665. encryptedKey = DoubleEncryptionFactory.getInstance().encrypt(encryptedKey,
  666. payload.getSharedKey(),
  667. secondPassword,
  668. payload.getOptions().getIterations());
  669. }
  670. final LegacyAddress legacyAddress = new LegacyAddress();
  671. legacyAddress.setEncryptedKey(encryptedKey);
  672. legacyAddress.setAddress(ecKey.toAddress(MainNetParams.get()).toString());
  673. legacyAddress.setCreatedDeviceName(deviceName);
  674. legacyAddress.setCreated(System.currentTimeMillis());
  675. legacyAddress.setCreatedDeviceVersion(deviceVersion);
  676. return legacyAddress;
  677. }
  678. public boolean addLegacyAddress(LegacyAddress legacyAddress){
  679. List<LegacyAddress> updatedLegacyAddresses = payload.getLegacyAddresses();
  680. updatedLegacyAddresses.add(legacyAddress);
  681. payload.setLegacyAddresses(updatedLegacyAddresses);
  682. return savePayloadToServer();
  683. }
  684. private ECKey getRandomECKey() {
  685. String result = null;
  686. byte[] data = null;
  687. try {
  688. result = WebUtil.getInstance().getURL(WebUtil.EXTERNAL_ENTROPY_URL);
  689. if (!result.matches("^[A-Fa-f0-9]{64}$")) {
  690. return null;
  691. }
  692. data = Hex.decode(result);
  693. } catch (Exception e) {
  694. return null;
  695. }
  696. ECKey ecKey = null;
  697. if (data != null) {
  698. byte[] rdata = new byte[32];
  699. SecureRandom random = new SecureRandom();
  700. random.nextBytes(rdata);
  701. byte[] privbytes = Util.getInstance().xor(data, rdata);
  702. if (privbytes == null) {
  703. return null;
  704. }
  705. ecKey = ECKey.fromPrivate(privbytes, true);
  706. // erase all byte arrays:
  707. random.nextBytes(privbytes);
  708. random.nextBytes(rdata);
  709. random.nextBytes(data);
  710. } else {
  711. return null;
  712. }
  713. return ecKey;
  714. }
  715. public String getHDSeed() throws IOException, MnemonicException.MnemonicLengthException {
  716. return wallet.getSeedHex();
  717. }
  718. public String getHDMnemonic() throws IOException, MnemonicException.MnemonicLengthException {
  719. return wallet.getMnemonic();
  720. }
  721. public String getHDPassphrase() throws IOException, MnemonicException.MnemonicLengthException {
  722. return wallet.getPassphrase();
  723. }
  724. public Address getAddressAt(int accountIndex, int chain, int addressIndex) {
  725. Address hd_address = null;
  726. if (!payload.isDoubleEncrypted()) {
  727. hd_address = wallet.getAccount(accountIndex).getChain(chain).getAddressAt(addressIndex);
  728. } else {
  729. hd_address = watchOnlyWallet.getAccount(accountIndex).getChain(chain).getAddressAt(addressIndex);
  730. }
  731. return hd_address;
  732. }
  733. }