/* * Copyright 2011-2012 the original author or authors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package piuk.blockchain.android; import java.math.BigInteger; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.json.simple.JSONObject; import org.json.simple.JSONValue; import org.json.simple.parser.JSONParser; import org.spongycastle.util.encoders.Hex; import piuk.blockchain.android.Constants; import piuk.blockchain.android.SuccessCallback; import piuk.blockchain.android.util.WalletUtils; import android.annotation.SuppressLint; import android.util.Pair; //import android.util.Log; import com.google.bitcoin.core.Address; import com.google.bitcoin.core.AddressFormatException; import com.google.bitcoin.core.Base58; import com.google.bitcoin.core.DumpedPrivateKey; import com.google.bitcoin.core.ECKey; import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.Sha256Hash; import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.core.TransactionInput; import com.google.bitcoin.core.TransactionOutput; import com.google.bitcoin.core.Utils; import com.google.bitcoin.core.Wallet; import com.google.bitcoin.params.MainNetParams; @SuppressLint("DefaultLocale") @SuppressWarnings("unchecked") public class MyRemoteWallet extends MyWallet { private static final String WebROOT = "https://"+Constants.BLOCKCHAIN_DOMAIN+"/"; private static final String ApiCode = "25a6ad13-1633-4dfb-b6ee-9b91cdf0b5c3"; public static final String NotificationsTypeEmail = "1"; public static final String NotificationsTypeSMS = "32"; private String _checksum; private boolean _isNew = false; private MyBlock latestBlock; private long lastMultiAddress; private BigInteger final_balance = BigInteger.ZERO; private BigInteger total_received = BigInteger.ZERO; private BigInteger total_sent = BigInteger.ZERO; private String language = "en"; private String btcCurrencyCode = "USD"; private String localCurrencyCode = "BTC"; private boolean sync_pubkeys = true; private double localCurrencyConversion; private double btcCurrencyConversion; private long serverTimeOffset = 0; private HashSet<String> notificationsTypeSet = new HashSet<String>(); private String smsNumber = null; private String email = null; private Map<String, JSONObject> multiAddrBalancesRoot; private JSONObject multiAddrRoot; private double sharedFee; private List<MyTransaction> transactions = Collections.synchronizedList(new ArrayList<MyTransaction>()); public byte[] extra_seed; public static enum FeePolicy { FeeOnlyIfNeeded, FeeForce, FeeNever } private State state = State.INPUT; public static enum State { INPUT, SENDING, SENT } public void setState(State state) { this.state = state; } public State getState() { return this.state; } public MyBlock getLatestBlock() { return latestBlock; } public void setFinal_balance(BigInteger final_balance) { this.final_balance = final_balance; } public void setTotal_received(BigInteger total_received) { this.total_received = total_received; } public void setTotal_sent(BigInteger total_sent) { this.total_sent = total_sent; } public long getLastMultiAddress() { return lastMultiAddress; } public void setLatestBlock(MyBlock latestBlock) { this.latestBlock = latestBlock; } public BigInteger getFinal_balance() { return final_balance; } public BigInteger getTotal_received() { return total_received; } public BigInteger getTotal_sent() { return total_sent; } public String getLocalCurrencyCode() { return localCurrencyCode; } public double getLocalCurrencyConversion() { return localCurrencyConversion; } public Map<String, JSONObject> getMultiAddrBalancesRoot() { return multiAddrBalancesRoot; } public BigInteger getBalanceOfAddress(String address) { if (multiAddrBalancesRoot == null) return null; JSONObject addressRoot = multiAddrBalancesRoot.get(address); return BigInteger.valueOf(((Number)addressRoot.get("final_balance")).longValue()); } public JSONObject getMultiAddrRoot() { return multiAddrRoot; } public void setMultiAddrRoot(JSONObject multiAddrRoot) { this.multiAddrRoot = multiAddrRoot; } public double getSharedFee() { return sharedFee; } public boolean isAddressMine(String address) { for (Map<String, Object> map : this.getKeysMap()) { String addr = (String) map.get("addr"); if (address.equals(addr)) return true; } return false; } public static class Latestblock { int height; int block_index; Hash hash; long time; } public synchronized BigInteger getBalance() { return final_balance; } public static String generateSharedAddress(String destination) throws Exception { StringBuilder args = new StringBuilder(); args.append("address=" + destination); args.append("&shared=true"); args.append("&format=plain"); args.append("&method=create"); final String response = postURL("https://"+Constants.BLOCKCHAIN_DOMAIN+"/api/receive", args.toString()); JSONObject object = (JSONObject) new JSONParser().parse(response); return (String)object.get("input_address"); } public synchronized BigInteger getBalance(String address) { if (this.multiAddrBalancesRoot != null && this.multiAddrBalancesRoot.containsKey(address)) { return BigInteger.valueOf(((Number)this.multiAddrBalancesRoot.get(address).get("final_balance")).longValue()); } return BigInteger.ZERO; } public boolean isNew() { return _isNew; } public MyRemoteWallet() throws Exception { super(); this.temporyPassword = null; this._checksum = null; this._isNew = true; } public MyRemoteWallet(JSONObject walletJSONObj, String password) throws Exception { super(walletJSONObj.get("payload").toString(), password); handleWalletPayloadObj(walletJSONObj); this.temporyPassword = password; this._checksum = new String(Hex.encode(MessageDigest.getInstance("SHA-256").digest(walletJSONObj.get("payload").toString().getBytes("UTF-8")))); this._isNew = false; } public MyRemoteWallet( String base64Payload, String password) throws Exception { super(base64Payload, password); this.temporyPassword = password; this._checksum = new String(Hex.encode(MessageDigest.getInstance("SHA-256").digest(base64Payload.getBytes("UTF-8")))); this._isNew = false; } private static String fetchURL(String URL) throws Exception { return WalletUtils.getURL(URL); } public static String postURL(String request, String urlParameters) throws Exception { if (urlParameters.length() > 0) { urlParameters += "&"; } //Include the Android API Code in POST requests urlParameters += "api_code="+ApiCode; return WalletUtils.postURL(request, urlParameters); } @Override public synchronized boolean addKey(ECKey key, String address, String label) throws Exception { boolean success = super.addKey(key, address, label); EventListeners.invokeWalletDidChange(); return success; } @Override public synchronized boolean addKey(ECKey key, String address, String label, String device_name, String device_version) throws Exception { boolean success = super.addKey(key, address, label, device_name, device_version); EventListeners.invokeWalletDidChange(); return success; } @Override public void setTag(String address, long tag) { super.setTag(address, tag); EventListeners.invokeWalletDidChange(); } @Override public synchronized boolean addWatchOnly(String address, String source) throws Exception { boolean success = super.addWatchOnly(address, source); EventListeners.invokeWalletDidChange(); return success; } public String[] getNotWatchOnlyActiveAddresses() { String[] from = getActiveAddresses(); List<String> notWatchOnlyActiveAddresses = new ArrayList<String>(from.length); for(int i = 0; i < from.length; i++){ try { if (! isWatchOnly(from[i])) notWatchOnlyActiveAddresses.add(from[i]); } catch (Exception e) { e.printStackTrace(); } } return notWatchOnlyActiveAddresses.toArray(new String[notWatchOnlyActiveAddresses.size()]); } @Override public synchronized boolean removeKey(ECKey key) { boolean success = super.removeKey(key); EventListeners.invokeWalletDidChange(); return success; } public List<MyTransaction> getMyTransactions() { return transactions; } public boolean addTransaction(MyTransaction tx) { for (MyTransaction existing_tx : transactions) { if (existing_tx.getTxIndex() == tx.getTxIndex()) return false; } this.transactions.add(tx); return true; } public boolean prependTransaction(MyTransaction tx) { for (MyTransaction existing_tx : transactions) { if (existing_tx.getTxIndex() == tx.getTxIndex()) return false; } this.transactions.add(0, tx); return true; } public BigInteger getBaseFee() { BigInteger baseFee = null; if (getFeePolicy() == -1) { baseFee = Utils.toNanoCoins("0.0001"); } else if (getFeePolicy() == 1) { baseFee = Utils.toNanoCoins("0.0005"); } else { baseFee = Utils.toNanoCoins("0.0001"); } return baseFee; } public List<MyTransaction> getTransactions() { return this.transactions; } public MyTransaction getTransaction(String txHash) { if (this.transactions == null) return null; for (Iterator<MyTransaction> iterator = this.transactions.iterator(); iterator.hasNext();) { MyTransaction tx = iterator.next(); if (tx.getHashAsString().equals(txHash)) return tx; } return null; } public void parseMultiAddr(String response, boolean notifications) throws Exception { transactions.clear(); BigInteger previousBalance = final_balance; Map<String, Object> top = (Map<String, Object>) JSONValue.parse(response); this.multiAddrRoot = (JSONObject) top; Map<String, Object> info_obj = (Map<String, Object>) top.get("info"); Map<String, Object> block_obj = (Map<String, Object>) info_obj.get("latest_block"); if (block_obj != null) { Sha256Hash hash = new Sha256Hash(Hex.decode((String)block_obj.get("hash"))); int blockIndex = ((Number)block_obj.get("block_index")).intValue(); int blockHeight = ((Number)block_obj.get("height")).intValue(); long time = ((Number)block_obj.get("time")).longValue(); MyBlock block = new MyBlock(); block.height = blockHeight; block.hash = hash; block.blockIndex = blockIndex; block.time = time; this.latestBlock = block; } List<JSONObject> multiAddrBalances = (List<JSONObject>) top.get("addresses"); Map<String, JSONObject> multiAddrBalancesRoot = new HashMap<String, JSONObject>(); for (JSONObject obj : multiAddrBalances) { multiAddrBalancesRoot.put((String) obj.get("address"), obj); } this.multiAddrBalancesRoot = multiAddrBalancesRoot; Map<String, Object> symbol_local = (Map<String, Object>) info_obj.get("symbol_local"); boolean didUpdateCurrency = false; if (symbol_local != null && symbol_local.containsKey("code")) { String currencyCode = (String) symbol_local.get("code"); Double currencyConversion = (Double) symbol_local.get("conversion"); if (currencyConversion == null) currencyConversion = 0d; if (this.localCurrencyCode == null || !this.localCurrencyCode.equals(currencyCode) || this.localCurrencyConversion != currencyConversion) { this.localCurrencyCode = currencyCode; this.localCurrencyConversion = currencyConversion; didUpdateCurrency = true; } } Map<String, Object> symbol_btc = (Map<String, Object>) info_obj.get("symbol_btc"); if (symbol_btc != null && symbol_btc.containsKey("code")) { String currencyCode = (String) symbol_local.get("code"); Double currencyConversion = (Double) symbol_local.get("conversion"); if (currencyConversion == null) currencyConversion = 0d; if (this.btcCurrencyCode == null || !this.btcCurrencyCode.equals(currencyCode) || this.btcCurrencyConversion != currencyConversion) { this.btcCurrencyCode = currencyCode; this.btcCurrencyConversion = currencyConversion; //didUpdateCurrency = true; } } if (didUpdateCurrency) { EventListeners.invokeCurrencyDidChange(); } if (top.containsKey("mixer_fee")) { sharedFee = ((Number)top.get("mixer_fee")).doubleValue(); } Map<String, Object> wallet_obj = (Map<String, Object>) top.get("wallet"); this.final_balance = BigInteger.valueOf(((Number)wallet_obj.get("final_balance")).longValue()); this.total_sent = BigInteger.valueOf(((Number)wallet_obj.get("total_sent")).longValue()); this.total_received = BigInteger.valueOf(((Number)wallet_obj.get("total_received")).longValue()); List<Map<String, Object>> transactions = (List<Map<String, Object>>) top.get("txs"); MyTransaction newestTransaction = null; if (transactions != null) { for (Map<String, Object> transactionDict : transactions) { MyTransaction tx = MyTransaction.fromJSONDict(transactionDict); if (tx == null) continue; if (newestTransaction == null) newestTransaction = tx; addTransaction(tx); } } if (notifications) { if (this.final_balance.compareTo(previousBalance) != 0 && newestTransaction != null) { if (newestTransaction.getResult().compareTo(BigInteger.ZERO) >= 0) EventListeners.invokeOnCoinsReceived(newestTransaction, newestTransaction.getResult().longValue()); else EventListeners.invokeOnCoinsSent(newestTransaction, newestTransaction.getResult().longValue()); } } else { EventListeners.invokeOnTransactionsChanged(); } } public boolean isUptoDate(long time) { long now = System.currentTimeMillis(); if (lastMultiAddress < now - time) { return false; } else { return true; } } public static synchronized String getBalances(String[] addresses, boolean notifications) throws Exception { String url = WebROOT + "multiaddr?active=" + StringUtils.join(addresses, "|") + "&simple=true&format=json"; String response = fetchURL(url); return response; } public synchronized String doMultiAddr(boolean notifications) throws Exception { String url = WebROOT + "multiaddr?active=" + StringUtils.join(getActiveAddresses(), "|") + "&symbol_btc="+btcCurrencyCode + "&symbol_local=" + localCurrencyCode; String response = fetchURL(url); parseMultiAddr(response, notifications); lastMultiAddress = System.currentTimeMillis(); return response; } public synchronized void doMultiAddr(boolean notifications, SuccessCallback callback) { try { String url = WebROOT + "multiaddr?active=" + StringUtils.join(getActiveAddresses(), "|") + "&symbol_btc=" + btcCurrencyCode + "&symbol_local=" + localCurrencyCode; String response = fetchURL(url); parseMultiAddr(response, notifications); lastMultiAddress = System.currentTimeMillis(); callback.onSuccess(); } catch (Exception e) { e.printStackTrace(); callback.onFail(); } } public interface SendProgress { public void onStart(); //Return false to cancel public boolean onReady(Transaction tx, BigInteger fee, FeePolicy feePolicy, long priority); public void onSend(Transaction tx, String message); //Return true to cancel the transaction or false to continue without it public ECKey onPrivateKeyMissing(String address); public void onError(String message); public void onProgress(String message); } private List<MyTransactionOutPoint> filter(List<MyTransactionOutPoint> unspent, List<ECKey> tempKeys, boolean askForPrivateKeys, final SendProgress progress) throws Exception { List<MyTransactionOutPoint> filtered = new ArrayList<MyTransactionOutPoint>(); Set<String> alreadyAskedFor = new HashSet<String>(); for (MyTransactionOutPoint output : unspent) { BitcoinScript script = new BitcoinScript(output.getScriptBytes()); String addr = script.getAddress().toString(); Map<String, Object> keyMap = findKey(addr); if (keyMap.get("priv") == null) { if (askForPrivateKeys && alreadyAskedFor.add(addr)) { ECKey key = progress.onPrivateKeyMissing(addr); if (key != null) { filtered.add(output); tempKeys.add(key); } } } else { filtered.add(output); } } return filtered; } private Pair<ECKey, String> generateNewMiniPrivateKey() { SecureRandom random = new SecureRandom(); if (extra_seed != null) { random.setSeed(extra_seed); } while (true) { // Log.d("generateNewMiniPrivateKey", "generateNewMiniPrivateKey"); //Make Candidate Mini Key byte randomBytes[] = new byte[16]; random.nextBytes(randomBytes); String encodedBytes = Base58.encode(randomBytes); //TODO: Casascius Series 1 22-character variant, remember to notify Ben about updating to 30-character variant String minikey = 'S' + encodedBytes.substring(0,21); //minikey = "S8osZG4hyGCsMxfxuTUfrF"; // canned data try { //Append ? & hash it again byte[] bytes_appended = MessageDigest.getInstance("SHA-256").digest((minikey + '?').getBytes()); //If zero byte then the key is valid if (bytes_appended[0] == 0) { try { //SHA256 byte[] bytes = MessageDigest.getInstance("SHA-256").digest(minikey.getBytes()); final ECKey eckey = new ECKey(bytes, null); final DumpedPrivateKey dumpedPrivateKey1 = eckey.getPrivateKeyEncoded(getParams()); String privateKey1 = Base58.encode(dumpedPrivateKey1.bytes); final String toAddress = eckey.toAddress(getParams()).toString(); /* Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: minikey: " + minikey); Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: privateKey: " + privateKey1); Log.d("sendCoinsToFriend", "generateNewMiniPrivateKey: address: " + toAddress); */ return new Pair<ECKey, String>(eckey, minikey); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } public void sendCoinsEmail(final String email, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final SendProgress progress) throws Exception { sendCoinsToFriend("email", email, amount, feePolicy, fee, progress); } public void sendCoinsSMS(final String number, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final SendProgress progress) throws Exception { sendCoinsToFriend("sms", number, amount, feePolicy, fee, progress); } private void sendCoinsToFriend(final String sendType,final String emailOrNumber, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final SendProgress progress) throws Exception { new Thread() { @Override public void run() { progress.onStart(); final List<ECKey> tempKeys = new ArrayList<ECKey>(); try { final String[] from = getActiveAddresses(); final ECKey key; Pair<ECKey, String> keyAndMiniKey = null; /* if (sendType == "sms") { keyAndMiniKey = generateNewMiniPrivateKey(); key = keyAndMiniKey.first; } else { key = generateECKey(); } */ keyAndMiniKey = generateNewMiniPrivateKey(); key = keyAndMiniKey.first; //Construct a new transaction progress.onProgress("Getting Unspent Outputs"); List<MyTransactionOutPoint> allUnspent = getUnspentOutputPoints(from); Pair<Transaction, Long> pair = null; progress.onProgress("Constructing Transaction"); final DumpedPrivateKey dumpedPrivateKey = key.getPrivateKeyEncoded(getParams()); String privateKey = Base58.encode(dumpedPrivateKey.bytes); final String toAddress = key.toAddress(getParams()).toString(); /* Log.d("sendCoinsToFriend", "sendCoinsToFriend: privateKey: " + privateKey); Log.d("sendCoinsToFriend", "sendCoinsToFriend: toAddress: " + toAddress); */ try { //Try without asking for watch only addresses List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, false, progress); HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>(); receivingAddresses.put(toAddress, amount); pair = makeTransaction(false, unspent, receivingAddresses, fee, null); //Transaction cancelled if (pair == null) return; } catch (InsufficientFundsException e) { //Try with asking for watch only List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, true, progress); HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>(); receivingAddresses.put(toAddress, amount); pair = makeTransaction(false, unspent, receivingAddresses, fee, null); //Transaction cancelled if (pair == null) return; } if (allUnspent != null) { Transaction tx = pair.first; progress.onProgress("Signing Inputs"); Wallet wallet = new Wallet(MainNetParams.get()); for (TransactionInput input : tx.getInputs()) { byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript(); String address = new BitcoinScript(scriptBytes).getAddress().toString(); final ECKey walletKey; try { walletKey = getECKey(address); } catch (Exception e) { // skip add Watch Only Bitcoin Address key because already accounted for later with tempKeys continue; } ECKey keyCompressed; ECKey keyUnCompressed; BigInteger priv = new BigInteger(walletKey.getPrivKeyBytes()); if (priv.compareTo(BigInteger.ZERO) >= 0) { keyCompressed = new ECKey(priv, null, true); keyUnCompressed = new ECKey(priv, null, false); } else { byte[] appendZeroByte = ArrayUtils.addAll(new byte[1], walletKey.getPrivKeyBytes()); BigInteger priv2 = new BigInteger(appendZeroByte); keyCompressed = new ECKey(priv2, null, true); keyUnCompressed = new ECKey(priv2, null, false); } if (keyCompressed != null) { wallet.addKey(keyCompressed); } if (keyUnCompressed != null) { wallet.addKey(keyUnCompressed); } } wallet.addKeys(tempKeys); //Now sign the inputs tx.signInputs(SigHash.ALL, wallet); progress.onProgress("Broadcasting Transaction"); final String txHash = tx.getHashAsString(); /* Log.d("sendCoinsToFriend", "sendCoinsToFriend: txHash: " + txHash); Log.d("sendCoinsToFriend", "sendCoinsToFriend: emailOrNumber: " + emailOrNumber); */ Map<Object, Object> params = new HashMap<Object, Object>(); params.put("type", sendType); // Log.d("sendCoinsToFriend", "type:" + sendType); String privParameter = (sendType == "sms") ? keyAndMiniKey.second : privateKey; params.put("priv", privParameter); params.put("hash", txHash); params.put("to", emailOrNumber); // Log.d("sendCoinsToFriend", "to:" + emailOrNumber); params.put("guid", getGUID()); params.put("sharedKey", getSharedKey()); try { String response = WalletUtils.postURLWithParams(WebROOT + "send-via", params); if (response != null && response.length() > 0) { progress.onProgress("Send Transaction"); // Log.d("sendCoinsToFriend", "sendCoinsToFriend: send-via response: " + response); String response2 = pushTx(tx); if (response2 != null && response2.length() > 0) { // Log.d("sendCoinsToFriend", "sendCoinsToFriend: pushTx response: " + response2); progress.onSend(tx, response2); String label = sendType == "email" ? emailOrNumber + " Sent Via Email" : emailOrNumber + " Sent Via SMS"; addKey(key, toAddress, label); setTag(toAddress, 2); } } } catch (Exception e) { progress.onError(e.getMessage()); e.printStackTrace(); } } } catch (Exception e) { progress.onError(e.getMessage()); e.printStackTrace(); } } }.start(); } public void simpleSendCoinsAsync(final String toAddress, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final SendProgress progress) { HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>(); receivingAddresses.put(toAddress, amount); final String[] from = getActiveAddresses(); HashMap<String, BigInteger> sendingAddresses = new HashMap<String, BigInteger>(); for(int i = 0; i < from.length; i++) sendingAddresses.put(from[i], null); sendCoinsAsync(true, sendingAddresses, receivingAddresses, feePolicy, fee, null, progress); } public void sendCoinsAsync(final String[] from, final String toAddress, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final String changeAddress, final SendProgress progress) { HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>(); receivingAddresses.put(toAddress, amount); HashMap<String, BigInteger> sendingAddresses = new HashMap<String, BigInteger>(); for(int i = 0; i < from.length; i++) sendingAddresses.put(from[i], null); sendCoinsAsync(false, sendingAddresses, receivingAddresses, feePolicy, fee, changeAddress, progress); } public void sendCoinsAsync(final HashMap<String, BigInteger> sendingAddresses, final String toAddress, final BigInteger amount, final FeePolicy feePolicy, final BigInteger fee, final String changeAddress, final SendProgress progress) { BigInteger sum = BigInteger.ZERO; for (Iterator<Entry<String, BigInteger>> iterator = sendingAddresses.entrySet().iterator(); iterator.hasNext();) { Entry<String, BigInteger> entry = iterator.next(); sum = sum.add(entry.getValue()); } if (sum.compareTo(amount) != 0) { progress.onError("Internal error input amounts not validating correctly"); return; } HashMap<String, BigInteger> receivingAddresses = new HashMap<String, BigInteger>(); receivingAddresses.put(toAddress, amount); sendCoinsAsync(false, sendingAddresses, receivingAddresses, feePolicy, fee, changeAddress, progress); } private void sendCoinsAsync(final boolean isSimpleSend, final HashMap<String, BigInteger> sendingAddresses, final HashMap<String, BigInteger> receivingAddresses, final FeePolicy feePolicy, final BigInteger fee, final String changeAddress, final SendProgress progress) { new Thread() { @Override public void run() { progress.onStart(); final BigInteger feeAmount; if (fee == null) feeAmount = BigInteger.ZERO; else feeAmount = fee; final List<ECKey> tempKeys = new ArrayList<ECKey>(); try { //Construct a new transaction progress.onProgress("Getting Unspent Outputs"); List<String> from = new ArrayList<String>(sendingAddresses.keySet()); List<MyTransactionOutPoint> allUnspent = getUnspentOutputPoints(from.toArray(new String[from.size()])); Pair<Transaction, Long> pair = null; progress.onProgress("Constructing Transaction"); try { //Try without asking for watch only addresses List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, false, progress); if (isSimpleSend) { pair = makeTransaction(isSimpleSend, unspent, receivingAddresses, feeAmount, changeAddress); } else { pair = makeTransactionCustom(sendingAddresses, unspent, receivingAddresses, feeAmount, changeAddress); } //Transaction cancelled if (pair == null) return; } catch (InsufficientFundsException e) { //Try with asking for watch only List<MyTransactionOutPoint> unspent = filter(allUnspent, tempKeys, true, progress); if (isSimpleSend) { pair = makeTransaction(isSimpleSend, unspent, receivingAddresses, feeAmount, changeAddress); } else { pair = makeTransactionCustom(sendingAddresses, unspent, receivingAddresses, feeAmount, changeAddress); } //Transaction cancelled if (pair == null) return; } Transaction tx = pair.first; Long priority = pair.second; if (isSimpleSend) { //If returns false user cancelled //Probably because they want to recreate the transaction with different fees if (!progress.onReady(tx, feeAmount, feePolicy, priority)) return; } progress.onProgress("Signing Inputs"); Wallet wallet = new Wallet(MainNetParams.get()); for (TransactionInput input : tx.getInputs()) { byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript(); String address = new BitcoinScript(scriptBytes).getAddress().toString(); final ECKey walletKey; try { walletKey = getECKey(address); } catch (Exception e) { // skip add Watch Only Bitcoin Address key because already accounted for later with tempKeys continue; } ECKey keyCompressed; ECKey keyUnCompressed; BigInteger priv = new BigInteger(walletKey.getPrivKeyBytes()); if (priv.compareTo(BigInteger.ZERO) >= 0) { keyCompressed = new ECKey(priv, null, true); keyUnCompressed = new ECKey(priv, null, false); } else { byte[] appendZeroByte = ArrayUtils.addAll(new byte[1], walletKey.getPrivKeyBytes()); BigInteger priv2 = new BigInteger(appendZeroByte); keyCompressed = new ECKey(priv2, null, true); keyUnCompressed = new ECKey(priv2, null, false); } if (keyCompressed != null) { wallet.addKey(keyCompressed); } if (keyUnCompressed != null) { wallet.addKey(keyUnCompressed); } } wallet.addKeys(tempKeys); //Now sign the inputs tx.signInputs(SigHash.ALL, wallet); progress.onProgress("Broadcasting Transaction"); String response = pushTx(tx); progress.onSend(tx, response); } catch (Exception e) { e.printStackTrace(); progress.onError(e.getLocalizedMessage()); } } }.start(); } //Returns response message public String pushTx(Transaction tx) throws Exception { String hexString = new String(Hex.encode(tx.bitcoinSerialize())); if (hexString.length() > 16384) throw new Exception("Blockchain wallet's cannot handle transactions over 16kb in size. Please try splitting your transaction"); String response = postURL(WebROOT + "pushtx", "tx="+hexString); return response; } public static class InsufficientFundsException extends Exception { private static final long serialVersionUID = 1L; public InsufficientFundsException(String string) { super(string); } } //You must sign the inputs public Pair<Transaction, Long> makeTransaction(boolean isSimpleSend, List<MyTransactionOutPoint> unspent, HashMap<String, BigInteger> receivingAddresses, BigInteger fee, final String changeAddress) throws Exception { long priority = 0; if (unspent == null || unspent.size() == 0) throw new InsufficientFundsException("No free outputs to spend."); if (fee == null) fee = BigInteger.ZERO; //Construct a new transaction Transaction tx = new Transaction(getParams()); BigInteger outputValueSum = BigInteger.ZERO; for (Iterator<Entry<String, BigInteger>> iterator = receivingAddresses.entrySet().iterator(); iterator.hasNext();) { Map.Entry<String, BigInteger> mapEntry = iterator.next(); String toAddress = mapEntry.getKey(); BigInteger amount = mapEntry.getValue(); if (amount == null || amount.compareTo(BigInteger.ZERO) <= 0) throw new Exception("You must provide an amount"); outputValueSum = outputValueSum.add(amount); //Add the output BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(toAddress)); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction toAddress: " + toAddress + "amount: " + amount); TransactionOutput output = new TransactionOutput(getParams(), null, amount, toOutputScript.getProgram()); tx.addOutput(output); } //Now select the appropriate inputs BigInteger valueSelected = BigInteger.ZERO; BigInteger valueNeeded = outputValueSum.add(fee); BigInteger minFreeOutputSize = BigInteger.valueOf(1000000); MyTransactionOutPoint changeOutPoint = null; for (MyTransactionOutPoint outPoint : unspent) { BitcoinScript script = new BitcoinScript(outPoint.getScriptBytes()); if (script.getOutType() == BitcoinScript.ScriptOutTypeStrange) continue; BitcoinScript inputScript = new BitcoinScript(outPoint.getConnectedPubKeyScript()); String address = inputScript.getAddress().toString(); //if isSimpleSend don't use address as input if is output if (isSimpleSend && receivingAddresses.get(address) != null) continue; MyTransactionInput input = new MyTransactionInput(getParams(), null, new byte[0], outPoint); input.outpoint = outPoint; // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction fromAddress: " + address + "amount: " + outPoint.value); tx.addInput(input); valueSelected = valueSelected.add(outPoint.value); priority += outPoint.value.longValue() * outPoint.confirmations; if (changeAddress == null) changeOutPoint = outPoint; if (valueSelected.compareTo(valueNeeded) == 0 || valueSelected.compareTo(valueNeeded.add(minFreeOutputSize)) >= 0) break; } //Check the amount we have selected is greater than the amount we need if (valueSelected.compareTo(valueNeeded) < 0) { throw new InsufficientFundsException("Insufficient Funds"); } BigInteger change = valueSelected.subtract(outputValueSum).subtract(fee); //Now add the change if there is any if (change.compareTo(BigInteger.ZERO) > 0) { BitcoinScript change_script; if (changeAddress != null) { change_script = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(changeAddress)); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction changeAddress != null: " + changeAddress + "change: " + change); } else if (changeOutPoint != null) { BitcoinScript inputScript = new BitcoinScript(changeOutPoint.getConnectedPubKeyScript()); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransaction changeAddress == null: " + inputScript.getAddress() + "change: " + change); //Return change to the first address change_script = BitcoinScript.createSimpleOutBitoinScript(inputScript.getAddress()); } else { throw new Exception("Invalid transaction attempt"); } TransactionOutput change_output = new TransactionOutput(getParams(), null, change, change_script.getProgram()); tx.addOutput(change_output); } long estimatedSize = tx.bitcoinSerialize().length + (114 * tx.getInputs().size()); priority /= estimatedSize; return new Pair<Transaction, Long>(tx, priority); } //You must sign the inputs public Pair<Transaction, Long> makeTransactionCustom(final HashMap<String, BigInteger> sendingAddresses, List<MyTransactionOutPoint> unspent, HashMap<String, BigInteger> receivingAddresses, BigInteger fee, final String changeAddress) throws Exception { long priority = 0; if (unspent == null || unspent.size() == 0) throw new InsufficientFundsException("No free outputs to spend."); if (fee == null) fee = BigInteger.ZERO; //Construct a new transaction Transaction tx = new Transaction(getParams()); BigInteger outputValueSum = BigInteger.ZERO; for (Iterator<Entry<String, BigInteger>> iterator = receivingAddresses.entrySet().iterator(); iterator.hasNext();) { Map.Entry<String, BigInteger> mapEntry = iterator.next(); String toAddress = mapEntry.getKey(); BigInteger amount = mapEntry.getValue(); if (amount == null || amount.compareTo(BigInteger.ZERO) <= 0) throw new Exception("You must provide an amount"); outputValueSum = outputValueSum.add(amount); //Add the output BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(toAddress)); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom toAddress: " + toAddress + "amount: " + amount); TransactionOutput output = new TransactionOutput(getParams(), null, amount, toOutputScript.getProgram()); tx.addOutput(output); } //Now select the appropriate inputs BigInteger valueSelected = BigInteger.ZERO; BigInteger valueNeeded = outputValueSum.add(fee); Map<String, BigInteger> addressTotalUnspentValues = new HashMap<String, BigInteger>(); for (MyTransactionOutPoint outPoint : unspent) { BitcoinScript script = new BitcoinScript(outPoint.getScriptBytes()); if (script.getOutType() == BitcoinScript.ScriptOutTypeStrange) continue; BitcoinScript inputScript = new BitcoinScript(outPoint.getConnectedPubKeyScript()); String address = inputScript.getAddress().toString(); BigInteger addressSendAmount = sendingAddresses.get(address); if (addressSendAmount == null) { throw new Exception("Invalid transaction address send amount is null"); } final BigInteger addressTotalUnspentValue = addressTotalUnspentValues.get(address); if (addressTotalUnspentValue == null) { addressTotalUnspentValues.put(address, outPoint.value); } else { addressTotalUnspentValues.put(address, addressTotalUnspentValue.add(outPoint.value)); } MyTransactionInput input = new MyTransactionInput(getParams(), null, new byte[0], outPoint); input.outpoint = outPoint; // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom fromAddress: " + address + "amount: " + outPoint.value); tx.addInput(input); valueSelected = valueSelected.add(outPoint.value); priority += outPoint.value.longValue() * outPoint.confirmations; //if (valueSelected.compareTo(valueNeeded) == 0 || valueSelected.compareTo(valueNeeded.add(minFreeOutputSize)) >= 0) // break; } //Check the amount we have selected is greater than the amount we need if (valueSelected.compareTo(valueNeeded) < 0) { throw new InsufficientFundsException("Insufficient Funds"); } //decide change if (changeAddress == null) { BigInteger feeAmountLeftToAccountedFor = fee; for (Iterator<Entry<String, BigInteger>> iterator = addressTotalUnspentValues.entrySet().iterator(); iterator.hasNext();) { final Entry<String, BigInteger> entry = iterator.next(); final String address = entry.getKey(); final BigInteger addressTotalUnspentValue = entry.getValue(); final BigInteger addressSendAmount = sendingAddresses.get(address); BigInteger addressChangeAmount = addressTotalUnspentValue.subtract(addressSendAmount); if (feeAmountLeftToAccountedFor.compareTo(BigInteger.ZERO) > 0) { if (addressChangeAmount.compareTo(feeAmountLeftToAccountedFor) >= 0) { //have enough to fill fee addressChangeAmount = addressChangeAmount.subtract(feeAmountLeftToAccountedFor); feeAmountLeftToAccountedFor = BigInteger.ZERO; } else { // do not have enough to fill fee feeAmountLeftToAccountedFor = feeAmountLeftToAccountedFor.subtract(addressChangeAmount); addressChangeAmount = BigInteger.ZERO; } } if (addressChangeAmount.compareTo(BigInteger.ZERO) > 0) { //Add the output BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(address)); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom changeAddress == null: " + address + "addressChangeAmount: " + addressChangeAmount); TransactionOutput output = new TransactionOutput(getParams(), null, addressChangeAmount, toOutputScript.getProgram()); tx.addOutput(output); } } } else { BigInteger addressChangeAmountSum = BigInteger.ZERO; for (Iterator<Entry<String, BigInteger>> iterator = addressTotalUnspentValues.entrySet().iterator(); iterator.hasNext();) { final Entry<String, BigInteger> entry = iterator.next(); final String address = entry.getKey(); final BigInteger addressTotalUnspentValue = entry.getValue(); final BigInteger addressSendAmount = sendingAddresses.get(address); final BigInteger addressChangeAmount = addressTotalUnspentValue.subtract(addressSendAmount); addressChangeAmountSum = addressChangeAmountSum.add(addressChangeAmount); } if (addressChangeAmountSum.compareTo(BigInteger.ZERO) > 0) { //Add the output BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(changeAddress)); TransactionOutput output = new TransactionOutput(getParams(), null, addressChangeAmountSum.subtract(fee), toOutputScript.getProgram()); // Log.d("MyRemoteWallet", "MyRemoteWallet makeTransactionCustom changeAddress != null: " + changeAddress + "addressChangeAmount: " + output.getValue()); tx.addOutput(output); } } long estimatedSize = tx.bitcoinSerialize().length + (114 * tx.getInputs().size()); priority /= estimatedSize; return new Pair<Transaction, Long>(tx, priority); } public static List<MyTransactionOutPoint> getUnspentOutputPoints(String[] from) throws Exception { StringBuffer buffer = new StringBuffer(WebROOT + "unspent?active="); int ii = 0; for (String address : from) { buffer.append(address); if (ii < from.length-1) buffer.append("|"); ++ii; } List<MyTransactionOutPoint> outputs = new ArrayList<MyTransactionOutPoint>(); String response = fetchURL(buffer.toString()); Map<String, Object> root = (Map<String, Object>) JSONValue.parse(response); List<Map<String, Object>> outputsRoot = (List<Map<String, Object>>) root.get("unspent_outputs"); for (Map<String, Object> outDict : outputsRoot) { byte[] hashBytes = Hex.decode((String)outDict.get("tx_hash")); ArrayUtils.reverse(hashBytes); Sha256Hash txHash = new Sha256Hash(hashBytes); int txOutputN = ((Number)outDict.get("tx_output_n")).intValue(); BigInteger value = BigInteger.valueOf(((Number)outDict.get("value")).longValue()); byte[] scriptBytes = Hex.decode((String)outDict.get("script")); int confirmations = ((Number)outDict.get("confirmations")).intValue(); //Contrstuct the output MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes); outPoint.setConfirmations(confirmations); outputs.add(outPoint); } return outputs; } /** * Register this account/device pair within the server. * @throws Exception * */ public boolean registerNotifications(final String regId) throws Exception { if (_isNew) return false; StringBuilder args = new StringBuilder(); args.append("guid=" + getGUID()); args.append("&sharedKey=" + getSharedKey()); args.append("&method=register-android-device"); args.append("&payload="+URLEncoder.encode(regId)); args.append("&length="+regId.length()); String response = postURL(WebROOT + "wallet", args.toString()); return response != null && response.length() > 0; } /** k * Unregister this account/device pair within the server. * @throws Exception */ public boolean unregisterNotifications(final String regId) throws Exception { if (_isNew) return false; StringBuilder args = new StringBuilder(); args.append("guid=" + getGUID()); args.append("&sharedKey=" + getSharedKey()); args.append("&method=unregister-android-device"); args.append("&payload="+URLEncoder.encode(regId)); args.append("&length="+regId.length()); String response = postURL(WebROOT + "wallet", args.toString()); return response != null && response.length() > 0; } public boolean getIsEmailNotificationEnabled() { return notificationsTypeSet.contains(NotificationsTypeEmail); } public boolean getIsSMSNotificationEnabled() { return notificationsTypeSet.contains(NotificationsTypeSMS); } public String getSmsNumber() { return smsNumber; } public void setSmsNumber(String smsNumber) { this.smsNumber = smsNumber; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public void getAccountInformation() throws Exception { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("method", "get-info"); params.put("format", "json"); String response = securePost(WebROOT + "wallet", params); JSONObject obj = (JSONObject) new JSONParser().parse(response); setEmail((String)obj.get("email")); setSmsNumber((String)obj.get("sms_number")); List<Long> notificationsType = (List<Long>) obj.get("notifications_type"); notificationsTypeSet = new HashSet<String>(); for (Long value : notificationsType) notificationsTypeSet.add(value.toString()); } public String updateEmail(String email) throws Exception { if (email == null) { throw new Exception("Email cannot be null"); } Map<Object, Object> params = new HashMap<Object, Object>(); String length = Integer.toString(email.length()); params.put("length", length); params.put("payload", email); params.put("method", "update-email"); String response = securePost(WebROOT + "wallet", params); return response; } public String updateSMS(String smsNumber) throws Exception { if (smsNumber == null) { throw new Exception("smsNumber cannot be null"); } Map<Object, Object> params = new HashMap<Object, Object>(); String length = Integer.toString(smsNumber.length()); params.put("length", length); params.put("payload", smsNumber); params.put("method", "update-sms"); String response = securePost(WebROOT + "wallet", params); return response; } public String enableEmailNotification(boolean enable) throws Exception { if (enable) notificationsTypeSet.add(NotificationsTypeEmail); else notificationsTypeSet.remove(NotificationsTypeEmail); List<String> list = new ArrayList<String>(notificationsTypeSet); return updateNotificationsType(list.toArray(new String[list.size()])); } public String enableSMSNotification(boolean enable) throws Exception { if (enable) notificationsTypeSet.add(NotificationsTypeSMS); else notificationsTypeSet.remove(NotificationsTypeSMS); List<String> list = new ArrayList<String>(notificationsTypeSet); return updateNotificationsType(list.toArray(new String[list.size()])); } public boolean isEnableEmailNotification() { return notificationsTypeSet.contains(NotificationsTypeEmail); } public boolean isEnableSMSNotification() { return notificationsTypeSet.contains(NotificationsTypeSMS); } public String updateNotificationsType(boolean enableEmailNotification, boolean enableSMSNotification) throws Exception { if (enableSMSNotification) notificationsTypeSet.add(NotificationsTypeSMS); else notificationsTypeSet.remove(NotificationsTypeSMS); if (enableEmailNotification) notificationsTypeSet.add(NotificationsTypeEmail); else notificationsTypeSet.remove(NotificationsTypeEmail); List<String> list = new ArrayList<String>(notificationsTypeSet); return updateNotificationsType(list.toArray(new String[list.size()])); } private String updateNotificationsType(String[] values) throws Exception { String payload = StringUtils.join(values, "|"); String length = Integer.toString(payload.length()); Map<Object, Object> params = new HashMap<Object, Object>(); params.put("length", length); params.put("payload", payload); params.put("method", "update-notifications-type"); String response = securePost(WebROOT + "wallet", params); return response; } public String securePost(String url, Map<Object, Object> data) throws Exception { Map<Object, Object> params = new HashMap<Object, Object>(data); if (! data.containsKey("sharedKey")) { serverTimeOffset = 500; //TODO dont hard code serverTimeOffset String sharedKey = getSharedKey().toLowerCase(); long now = new Date().getTime(); long timestamp = (now - serverTimeOffset) / 10000; String text = sharedKey + Long.toString(timestamp); String SKHashHex = new String(Hex.encode(MessageDigest.getInstance("SHA-256").digest(text.getBytes("UTF-8")))); int i = 0; String tSKUID = SKHashHex.substring(i, i+=8)+"-"+SKHashHex.substring(i, i+=4)+"-"+SKHashHex.substring(i, i+=4)+"-"+SKHashHex.substring(i, i+=4)+"-"+SKHashHex.substring(i, i+=12); params.put("sharedKey", tSKUID); params.put("sKTimestamp", Long.toString(timestamp)); params.put("sKDebugHexHash", SKHashHex); params.put("sKDebugTimeOffset", Long.toString(serverTimeOffset)); params.put("sKDebugOriginalClientTime", Long.toString(now)); params.put("sKDebugOriginalSharedKey", sharedKey); if (! params.containsKey("guid")) params.put("guid", this.getGUID()); if (! params.containsKey("format")) params.put("format", "plain"); } String response = WalletUtils.postURLWithParams(url, params); return response; } public boolean updateRemoteLocalCurrency(String currency_code) throws Exception { if (_isNew) return false; localCurrencyCode = currency_code; StringBuilder args = new StringBuilder(); args.append("guid=" + getGUID()); args.append("&sharedKey=" + getSharedKey()); args.append("&payload=" + currency_code); args.append("&length=" + currency_code.length()); args.append("&method=update-currency");; String response = postURL(WebROOT + "wallet", args.toString()); return response != null; } /** * Get the tempoary paring encryption password * @throws Exception * */ public static String getPairingEncryptionPassword(final String guid) throws Exception { StringBuilder args = new StringBuilder(); args.append("guid=" + guid); args.append("&method=pairing-encryption-password"); return postURL(WebROOT + "wallet", args.toString()); } public static BigInteger getAddressBalance(final String address) throws Exception { return new BigInteger(fetchURL(WebROOT + "q/addressbalance/"+address)); } public static String getWalletManualPairing(final String guid) throws Exception { StringBuilder args = new StringBuilder(); args.append("guid=" + guid); args.append("&method=pairing-encryption-password"); String response = fetchURL(WebROOT + "wallet/" + guid + "?format=json&resend_code=false"); JSONObject object = (JSONObject) new JSONParser().parse(response); String payload = (String) object.get("payload"); if (payload == null || payload.length() == 0) { throw new Exception("Error Fetching Wallet Payload"); } return payload; } public synchronized boolean remoteSave() throws Exception { return remoteSave(null); } public synchronized boolean remoteSave(String email) throws Exception { String payload = this.getPayload(); String old_checksum = this._checksum; this._checksum = new String(Hex.encode(MessageDigest.getInstance("SHA-256").digest(payload.getBytes("UTF-8")))); String method = _isNew ? "insert" : "update"; String urlEncodedPayload = URLEncoder.encode(payload); StringBuilder args = new StringBuilder(); args.append("guid="); args.append(URLEncoder.encode(this.getGUID(), "utf-8")); args.append("&sharedKey="); args.append(URLEncoder.encode(this.getSharedKey(), "utf-8")); args.append("&payload="); args.append(urlEncodedPayload); args.append("&method="); args.append(method); args.append("&length="); args.append(payload.length()); args.append("&checksum="); args.append(URLEncoder.encode(_checksum, "utf-8")); if (sync_pubkeys) { args.append("&active="); args.append(StringUtils.join(getActiveAddresses(), "|")); } if (email != null && email.length() > 0) { args.append("&email="); args.append(URLEncoder.encode(email, "utf-8")); } args.append("&device="); args.append("android"); if (old_checksum != null && old_checksum.length() > 0) { args.append("&old_checksum="); args.append(old_checksum); } postURL(WebROOT + "wallet", args.toString()); _isNew = false; return true; } public void remoteDownload() { } public String getChecksum() { return _checksum; } public synchronized String setPayload(JSONObject walletJSONObj) throws Exception { handleWalletPayloadObj(walletJSONObj); return setPayload(walletJSONObj.get("payload").toString()); } public synchronized String setPayload(String payload) throws Exception { MyRemoteWallet tempWallet = new MyRemoteWallet(payload, temporyPassword); this.root = tempWallet.root; this.rootContainer = tempWallet.rootContainer; if (this.temporySecondPassword != null && !this.validateSecondPassword(temporySecondPassword)) { this.temporySecondPassword = null; } this._checksum = tempWallet._checksum; _isNew = false; return payload; } public static class NotModfiedException extends Exception { private static final long serialVersionUID = 1L; } public void handleWalletPayloadObj(JSONObject obj) { Map<String, Object> symbol_local = (Map<String, Object>) obj.get("symbol_local"); boolean didUpdateCurrency = false; if (symbol_local != null && symbol_local.containsKey("code")) { String currencyCode = (String) symbol_local.get("code"); Double currencyConversion = (Double) symbol_local.get("conversion"); if (currencyConversion == null) currencyConversion = 0d; if (this.localCurrencyCode == null || !this.localCurrencyCode.equals(currencyCode) || this.localCurrencyConversion != currencyConversion) { this.localCurrencyCode = currencyCode; this.localCurrencyConversion = currencyConversion; didUpdateCurrency = true; } } Map<String, Object> symbol_btc = (Map<String, Object>) obj.get("symbol_btc"); if (symbol_btc != null && symbol_btc.containsKey("code")) { String currencyCode = (String) symbol_local.get("code"); Double currencyConversion = (Double) symbol_local.get("conversion"); if (currencyConversion == null) currencyConversion = 0d; if (this.btcCurrencyCode == null || !this.btcCurrencyCode.equals(currencyCode) || this.btcCurrencyConversion != currencyConversion) { this.btcCurrencyCode = currencyCode; this.btcCurrencyConversion = currencyConversion; //didUpdateCurrency = true; } } if (didUpdateCurrency) { EventListeners.invokeCurrencyDidChange(); } if (obj.containsKey("sync_pubkeys")) { sync_pubkeys = Boolean.valueOf(obj.get("sync_pubkeys").toString()); } } public static JSONObject getWalletPayload(String guid, String sharedKey, String checkSumString) throws Exception { String response = postURL(WebROOT + "wallet", "method=wallet.aes.json&guid="+guid+"&sharedKey="+sharedKey+"&checksum="+checkSumString+"&format=json"); if (response == null) { throw new Exception("Error downloading wallet"); } JSONObject obj = (JSONObject) new JSONParser().parse(response); String payload = obj.get("payload").toString(); if (payload == null) { throw new Exception("Error downloading wallet"); } if (payload.equals("Not modified")) { throw new NotModfiedException(); } return obj; } public static JSONObject getWalletPayload(String guid, String sharedKey) throws Exception { String response = postURL(WebROOT + "wallet","method=wallet.aes.json&guid="+guid+"&sharedKey="+sharedKey+"&format=json"); if (response == null) { throw new Exception("Error downloading wallet"); } JSONObject obj = (JSONObject) new JSONParser().parse(response); String payload = obj.get("payload").toString(); if (payload == null) { throw new Exception("Error downloading wallet"); } return obj; } public List<Pair<String, String>> getLabelList() { List<Pair<String, String>> array = new ArrayList<Pair<String, String>>(); Map<String, String> labelMap = this.getLabelMap(); synchronized(labelMap) { for (Map.Entry<String, String> entry : labelMap.entrySet()) { array.add(new Pair<String, String>(entry.getValue(), entry.getKey()) { public String toString() { return first.toString(); } }); } } return array; } public String getToAddress(String inputAddress) { final String userEntered = inputAddress; if (userEntered.length() > 0) { try { new Address(Constants.NETWORK_PARAMETERS, userEntered); return userEntered; } catch (AddressFormatException e) { List<Pair<String, String>> labels = this.getLabelList(); for (Pair<String, String> label : labels) { if (label.first.toLowerCase(Locale.ENGLISH).equals(userEntered.toLowerCase(Locale.ENGLISH))) { try { new Address(Constants.NETWORK_PARAMETERS, label.second); return label.second; } catch (AddressFormatException e1) {} } } } } return null; } }