package piuk.blockchain.android; import info.blockchain.wallet.ui.ObjectSuccessCallback; import info.blockchain.wallet.ui.SharedCoinSuccessCallback; import java.math.BigDecimal; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.ArrayUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.spongycastle.util.encoders.Hex; 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.Message; import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.ProtocolException; import com.google.bitcoin.core.Transaction; import com.google.bitcoin.core.TransactionInput; import com.google.bitcoin.core.TransactionOutPoint; import com.google.bitcoin.core.TransactionOutput; import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.crypto.TransactionSignature; import com.google.bitcoin.script.Script; import android.annotation.SuppressLint; import android.util.Log; import android.util.Pair; import piuk.blockchain.android.WalletApplication.AddAddressCallback; import piuk.blockchain.android.util.WalletUtils; public class SharedCoin { //private static final boolean SHARED_COIN_DEBUG = true; private static final boolean SHARED_COIN_DEBUG = false; static private Long getLongFromLong(JSONObject obj, String key) { return (Long) obj.get(key); } static private BigInteger getBigIntegerFromLong(JSONObject obj, String key) { return BigInteger.valueOf((Long) obj.get(key)); } static private String getIntegerStringFromIntegerString(JSONObject obj, String key) { return (String) obj.get(key); } static private BigInteger getIntegerFromIntegerString(JSONObject obj, String key) { return new BigInteger((String) obj.get(key)); } private static void setTimeout(long milliseconds) { try { Log.d("SharedCoin", "SharedCoin etTimeout b4 " + milliseconds); Thread.sleep(milliseconds); Log.d("SharedCoin", "SharedCoin etTimeout ad " + milliseconds); } catch (InterruptedException e) { e.printStackTrace(); } } private static byte[] signInput(NetworkParameters params, Transaction tx, int tx_input_index, String base58PrivKey, BitcoinScript script, SigHash sigHash) { Log.d("SharedCoin", "SharedCoin signInput tx.getInputs().size " + tx.getInputs().size()); Log.d("SharedCoin", "SharedCoin signInput tx_input_index " + tx_input_index); Log.d("SharedCoin", "SharedCoin signInput base58PrivKey " + base58PrivKey); try { ECKey key = new ECKey(Base58.decode(base58PrivKey), null); Log.d("SharedCoin", "SharedCoin signInput key.toAddress " + key.toAddress(params).toString()); TransactionSignature transactionSignature = tx.calculateSignature(tx_input_index, key, null, script.getProgram(), SigHash.ALL, false); byte[] signedScript = Script.createInputScript(transactionSignature.encodeToBitcoin(), key.getPubKey()); //ArrayUtils.reverse(signedScript); String signedScriptHex = new String(Hex.encode(signedScript)); Log.d("SharedCoin", "SharedCoin signInput signedScriptHex " + signedScriptHex); Log.d("SharedCoin", "SharedCoin signInput script.program hex " + new String(Hex.encode(script.getProgram()))); return signedScript; } catch (Exception e) { Log.d("SharedCoin", "SharedCoin signInput e " + e.getLocalizedMessage()); e.printStackTrace(); } return null; } private final static String STATUS_WAITING = "waiting"; private final static String STATUS_NOT_FOUND = "not_found"; private final static String STATUS_ACTIVE_PROPOSAL = "active_proposal"; private final static String STATUS_COMPLETE = "complete"; private final static String STATUS_SIGNATURES_NEEDED = "signatures_needed"; private final static String STATUS_VERIFICATION_FAILED = "verification_failed"; private final static String STATUS_SIGNATURES_ACCEPTED = "signatures_accepted"; //private static String SHARED_COIN_ENDPOINT = "https://api.sharedcoin.com?"; private static String SHARED_COIN_ENDPOINT = "https://api.sharedcoin.com/"; public static final int VERSION = 3; private static final String SEED_PREFIX = "sharedcoin-seed:"; private static final long MIN_TIME_BETWEEN_SUBMITS = 120000; private static final int SATOSHI = 100000000; private static SharedCoin instance = null; private Map<String, String> extra_private_keys; //{address : base58privkey} private NetworkParameters params = null; private long lastSignatureSubmitTime = 0; private JSONObject info = null; private MyRemoteWallet remoteWallet; private WalletApplication application; public SharedCoin(WalletApplication application, MyRemoteWallet remoteWallet) { this.extra_private_keys = new HashMap<String,String>(); this.remoteWallet = remoteWallet; this.application = application; this.params = MyRemoteWallet.getParams(); } public static SharedCoin getInstance(WalletApplication application, MyRemoteWallet remoteWallet) { if(instance == null) { instance = new SharedCoin(application, remoteWallet); } return instance; } private long getLastSignatureSubmitTime() { return this.lastSignatureSubmitTime; } private void setLastSignatureSubmitTime(long lastSignatureSubmitTime) { this.lastSignatureSubmitTime = lastSignatureSubmitTime; } private JSONObject _pollForCompleted(final BigInteger proposalID) { return SharedCoin.pollForProposalCompleted(proposalID); } private void pollForCompleted(final BigInteger proposalID, final SharedCoinSuccessCallback sharedCoinSuccessCallback) { while (true) { Log.d("SharedCoin", "SharedCoin pollForCompleted"); JSONObject obj = this._pollForCompleted(proposalID); if (obj == null) { Log.d("SharedCoin", "SharedCoin pollForCompleted _pollForCompleted error"); break; } String status = (String) obj.get("status"); Log.d("SharedCoin", "SharedCoin pollForCompleted getOfferID status " + status); if (status.equals(STATUS_WAITING)) { Log.d("SharedCoin", "SharedCoin pollForCompleted waiting continue"); continue; } else if (status.equals(STATUS_NOT_FOUND)) { sharedCoinSuccessCallback.onFail("Proposal ID Not Found"); break; } else if (status.equals(STATUS_COMPLETE)) { String txHash = (String) obj.get("tx_hash"); String tx = (String) obj.get("tx"); sharedCoinSuccessCallback.onComplete(txHash, tx); break; } else { sharedCoinSuccessCallback.onFail("Unknown status " + status); break; } } } private String generateAddressFromCustomSeed(String seed, int n) { Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed -------------------------------"); Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed seed: " + seed); Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed n: " + n); String seedn = seed + Integer.toString(n); try { byte[] hash = MessageDigest.getInstance("SHA-256").digest(seedn.getBytes()); ECKey key; BigInteger num = new BigInteger(hash); if (num.compareTo(BigInteger.ZERO) >= 0) { // condition is needed to match sharedCoin.js implementation if (hash[0] % 2 == 0) { key = new ECKey(num, null, false); } else { // keep in mind private key will not match in js version, but address will match // if compressed parameter was false instead private key will match in js version, but address will not match key = new ECKey(num, null, true); } } else { Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed: appendZeroByte"); // Prepend a zero byte to make the BigInteger positive byte[] appendZeroByte = ArrayUtils.addAll(new byte[1], hash); // condition is needed to match sharedCoin.js implementation if (hash[0] % 2 == 0) { key = new ECKey(new BigInteger(appendZeroByte), null, false); } else { // keep in mind private key will not match in js version, but address will match // if compressed parameter was false instead private key will match in js version, but address will not match key = new ECKey(new BigInteger(appendZeroByte), null, true); } } String address = key.toAddress(this.params).toString(); final DumpedPrivateKey dumpedPrivateKey = key.getPrivateKeyEncoded(params); String privateKey = Base58.encode(dumpedPrivateKey.bytes); Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed: privateKey " + privateKey); Log.d("SharedCoin", "SharedCoin generateAddressFromCustomSeed: address " + address); this.extra_private_keys.put(address, privateKey); return address; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } private void recover(final SuccessCallback successCallback, int key, final List<String> addresses) { Log.d("SharedCoin", "SharedCoin recover ------------------ "); Log.d("SharedCoin", "SharedCoin recover addresses " + addresses.toString()); Log.d("SharedCoin", "SharedCoin recover key " + key); this.application.getBalances(addresses.toArray(new String[addresses.size()]), false, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { long total_balance = 0; JSONObject results = (JSONObject) obj; Log.d("SharedCoin", "SharedCoin recover getBalances results " + results.toString()); for (final String address : addresses) { JSONObject addressDict = (JSONObject) results.get(address); long balance = SharedCoin.getLongFromLong(addressDict, "final_balance"); Log.d("SharedCoin", "SharedCoin recover address: " + address + " balance: " + balance); if (balance > 0) { Log.d("SharedCoin", "SharedCoin recover extra_private_keys.get(address): " + extra_private_keys.get(address)); try { byte[] privateKeyBytes = Base58.decode(extra_private_keys.get(address)); ECKey ecKey = new ECKey(privateKeyBytes, null); Log.d("SharedCoin", "SharedCoin recover ecKey toAddress: " + ecKey.toAddress(SharedCoin.this.params).toString()); application.addKeyToWallet(ecKey, address, null, 0, new AddAddressCallback(){ @Override public void onSavedAddress( String address) { Log.d("SharedCoin", "SharedCoin Imported: " + address); } @Override public void onError(String reason) { Log.d("SharedCoin", "SharedCoin Error importing: " + address + " " + reason); } }); } catch (AddressFormatException e) { Log.d("SharedCoin", "SharedCoin recover AddressFormatException: " + e.getLocalizedMessage()); e.printStackTrace(); } total_balance += balance; } Log.d("SharedCoin", "SharedCoin recover " + total_balance + " recovered from intermediate addresses"); if (total_balance > 0) { application.saveWallet(new SuccessCallback() { @Override public void onSuccess() { Log.d("SharedCoin", "SharedCoin recover saveWallet: onSuccess"); EventListeners.invokeWalletDidChange(); } @Override public void onFail() { Log.d("SharedCoin", "SharedCoin recover saveWallet: onFail"); } }); } } successCallback.onSuccess(); } @Override public void onFail(String error) { Log.d("SharedCoin", "SharedCoin recover getBalances " + error); successCallback.onFail(); } }); } private void doNext(final List<String> shared_coin_seeds, final SuccessCallback successCallback, int key, List<String> addresses) { Log.d("SharedCoin", "SharedCoin doNext ------------"); String seed = shared_coin_seeds.get(key); ++key; final int keyTmp = key; //for (int i = 0; i < 1; ++i) { //debug for (int i = 0; i < 100; ++i) { String address = generateAddressFromCustomSeed(seed, i); addresses.add(address); } final List<String> addressesTmp = addresses; if (key == shared_coin_seeds.size()) { Log.d("SharedCoin", "SharedCoin doNext key == shared_coin_seeds.size() "); while(addresses.size() > 0) { Log.d("SharedCoin", "SharedCoin doNext addresses1 " + addresses.toString()); Log.d("SharedCoin", "SharedCoin doNext addresses.size1 " + addresses.size()); recover(successCallback, key, addresses); // addresses = addresses.subList(1000, addresses.size()); addresses = new ArrayList<String>(); Log.d("SharedCoin", "SharedCoin doNext addresses2 " + addresses.toString()); Log.d("SharedCoin", "SharedCoin doNext addresses.size2 " + addresses.size()); } } else { Log.d("SharedCoin", "SharedCoin doNext key != shared_coin_seeds.size() "); SharedCoin.setTimeout(100); doNext(shared_coin_seeds, successCallback, keyTmp, addressesTmp); } } public void recoverSeeds(final List<String> shared_coin_seeds, final SuccessCallback successCallback) { Log.d("SharedCoin", "SharedCoin recoverSeeds ------------------"); int key = 0; List<String> addresses = new ArrayList<String>(); SharedCoin.setTimeout(100); doNext(shared_coin_seeds, successCallback, key, addresses); } private void error(final String errorMsg, Plan plan, final ObjectSuccessCallback objectSuccessCallback) { List<String> shared_coin_seeds = new ArrayList<String>(); shared_coin_seeds.add(SharedCoin.SEED_PREFIX+plan.address_seed); Log.d("SharedCoin", "SharedCoin misc-error " + errorMsg); Log.d("SharedCoin", "SharedCoin Recover Seed"); SharedCoin.setTimeout(2000); if (plan != null && plan.c_stage >= 0) { recoverSeeds(shared_coin_seeds, new SuccessCallback() { @Override public void onSuccess() { Log.d("SharedCoin", "SharedCoin Recover Success"); objectSuccessCallback.onFail("Error With SharedCoin, but SharedCoin Recover Success"); } @Override public void onFail() { Log.d("SharedCoin", "SharedCoin Recover Error"); objectSuccessCallback.onFail("SharedCoin Recover Error"); } }); } } public void sendSharedCoin(final int repetitions, final List<String> fromAddresses, final BigInteger amount, final String toAddress, final ObjectSuccessCallback objectSuccessCallback) throws Exception { if (repetitions <= 0) { throw new Exception("invalid number of repetitions"); } if (amount == null || amount.intValue() <= 0) { throw new Exception("You must enter a value greater than zero"); } List<HashMap<String,String>> toAddresses = new ArrayList<HashMap<String,String>>(); HashMap<String,String> map = new HashMap<String,String>(); map.put("value", amount.toString()); map.put("address", toAddress); toAddresses.add(map); long timeSinceLastSubmit = new Date().getTime() - getLastSignatureSubmitTime(); long interval = Math.max(0, MIN_TIME_BETWEEN_SUBMITS - timeSinceLastSubmit); Log.d("SharedCoin", "SharedCoin constructPlan timeSinceLastSubmit " + timeSinceLastSubmit); Log.d("SharedCoin", "SharedCoin constructPlan getLastSignatureSubmitTime() " + getLastSignatureSubmitTime()); Log.d("SharedCoin", "SharedCoin constructPlan interval " + interval); SharedCoin.setTimeout(interval); constructPlan(repetitions, fromAddresses, toAddresses, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin Created Plan"); final Plan plan = (Plan) obj; plan.execute(new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { final Plan plan = (Plan) obj; plan.execute(new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { //MyWallet.makeNotice('success', 'misc-success', 'Sharedcoin Transaction Successfully Completed'); //Toast Log.d("SharedCoin", "Sharedcoin Transaction Successfully Completed"); objectSuccessCallback.onSuccess("Transaction Successfully Completed"); } @Override public void onFail(String error) { Log.d("SharedCoin", "SharedCoin plan.execute: error: " + error); error(error, plan, objectSuccessCallback); } }); } @Override public void onFail(String error) { error(error, plan, objectSuccessCallback); } }); } @Override public void onFail(String error) { Log.d("SharedCoin", "SharedCoin constructPlan error " + error); } }); } private void constructPlan(int repetitions, List<String> fromAddresses, List<HashMap<String,String>> to_addresses, final ObjectSuccessCallback objectSuccessCallback) { final Plan plan = new Plan(this); try { List<BigInteger> to_values_before_fees = new ArrayList<BigInteger>(to_addresses.size()); //List<BigInteger> fee_each_repetition = new ArrayList<BigInteger>(repetitions); BigInteger[] fee_each_repetition = new BigInteger[repetitions]; Log.d("SharedCoin", "SharedCoin constructPlan: 1"); HashMap<String,BigInteger> to_addressesMap = new HashMap<String,BigInteger>(); for (HashMap<String,String> to_address : to_addresses) { BigInteger amt = new BigInteger(to_address.get("value")); to_addressesMap.put(to_address.get("address"), amt); to_values_before_fees.add(amt); //to_values_before_fees.add(to_address.get("amount")); Log.d("SharedCoin", "SharedCoin to_values_before_fees: " + to_values_before_fees); Log.d("SharedCoin", "SharedCoin constructPlan: 2"); for (int ii = repetitions-1; ii >= 0; --ii) { BigInteger feeThisOutput = calculateFeeForValue(amt); //BigInteger feeThisOutput = new BigInteger("777"); BigInteger existing = fee_each_repetition[ii]; if (existing != null) { fee_each_repetition[ii] = existing.add(feeThisOutput); } else { fee_each_repetition[ii] = feeThisOutput; } } } BigInteger feeSum = BigInteger.ZERO; for (int i = 0; i < fee_each_repetition.length; i++) feeSum = feeSum.add(fee_each_repetition[i]); Log.d("SharedCoin", "SharedCoin to_values_before_fees: " + Arrays.toString(to_values_before_fees.toArray())); Log.d("SharedCoin", "SharedCoin fee_each_repetition: " + Arrays.toString(fee_each_repetition)); //ECKey change_key = new ECKey(); final String change_address; Offer offer = new Offer(this); List<MyTransactionOutPoint> unspent; if (! SHARED_COIN_DEBUG) { unspent = MyRemoteWallet.getUnspentOutputPoints(fromAddresses.toArray(new String[fromAddresses.size()])); change_address = plan.generateAddressFromSeed(); } else { unspent = MyRemoteWallet.getUnspentOutputPoints(fromAddresses.toArray(new String[fromAddresses.size()])); change_address = plan.generateAddressFromSeed(); /* unspent = new ArrayList<MyTransactionOutPoint>(); String tx_hash = "6e659f6cb8a160c55e65d589c85bdbaa9cd0b6bfb2e68939f5ad68ffa6fe9c84"; byte[] hashBytes = Hex.decode(tx_hash); ArrayUtils.reverse(hashBytes); Sha256Hash txHash = new Sha256Hash(hashBytes); byte[] scriptBytes = Hex.decode("76a91423757b5b32e39b05fb5280aba05cdee5c6acd91088ac"); int txOutputN = 0; BigInteger value = new BigInteger("12507377"); MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes); unspent.add(outPoint); change_address = "1EQc2WicMjAV9mox4oMRx7sb6ivNYweeNR"; //*/ } Log.d("SharedCoin", "SharedCoin makeTransaction: 1"); Pair<Transaction, Long> pair = this.remoteWallet.makeTransaction(false, unspent, to_addressesMap, feeSum, change_address); Log.d("SharedCoin", "SharedCoin makeTransaction: 2"); Transaction transaction = pair.first; /* for (int i = 0; i < unspent.size(); i++) { MyTransactionOutPoint myTransactionOutPoint = unspent.get(i); myTransactionOutPoint.getTxHash(); } //*/ List<TransactionInput> transactionInputs = transaction.getInputs(); for (Iterator<TransactionInput> iti = transactionInputs.iterator(); iti.hasNext();) { TransactionInput transactionInput = iti.next(); TransactionOutPoint transactionOutPoint = transactionInput.getOutpoint(); if (transactionOutPoint instanceof MyTransactionOutPoint) { MyTransactionOutPoint myTransactionOutPoint = (MyTransactionOutPoint)transactionOutPoint; Log.d("SharedCoin", "SharedCoin myTransactionOutPoint.getTxHash(): " + myTransactionOutPoint.getTxHash()); Log.d("SharedCoin", "SharedCoin myTransactionOutPoint.getIndex(): " + myTransactionOutPoint.getIndex()); Log.d("SharedCoin", "SharedCoin myTransactionOutPoint.getValue(): " + myTransactionOutPoint.getValue()); offer.addOfferedOutpoint(myTransactionOutPoint.getTxHash().toString(), myTransactionOutPoint.getIndex(), myTransactionOutPoint.getValue().toString()); Log.d("SharedCoin", "SharedCoin -----------------------------------"); } else { throw new Exception("transactionOutPoint not instanceof MyTransactionOutPoint"); } } List<TransactionOutput> transactionOutputs = transaction.getOutputs(); for (Iterator<TransactionOutput> ito = transactionOutputs.iterator(); ito.hasNext();) { TransactionOutput transactionOutput = ito.next(); com.google.bitcoin.script.Script script = transactionOutput.getScriptPubKey(); String addr = script.getToAddress(this.params).toString(); //com.google.bitcoin.core.Script script = transactionOutput.getScriptPubKey(); //String addr = script.getToAddress().toString(); BigInteger value = transactionOutput.getValue(); BitcoinScript toOutputScript = new BitcoinScript(transactionOutput.getScriptBytes()); //BitcoinScript toOutputScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(addr)); byte[] program = toOutputScript.getProgram(); String programHex = new String(Hex.encode(program)); if (addr.equals(change_address)) { offer.addRequestOutputs(value.toString(), programHex, true); } else { offer.addRequestOutputs(value.toString(), programHex); } Log.d("SharedCoin", "SharedCoin transactionOutput programHex: " + programHex); Log.d("SharedCoin", "SharedCoin transactionOutput getValue: " + value); Log.d("SharedCoin", "SharedCoin transactionOutput addr: " + addr); Log.d("SharedCoin", "SharedCoin -----------------------------------"); } Log.d("SharedCoin", "SharedCoin constructPlan getOfferedOutpoint " + offer.getOfferedOutpoints().toString()); Log.d("SharedCoin", "SharedCoin constructPlan getRequestOutputs " + offer.getRequestOutputs().toString()); Log.d("SharedCoin", "SharedCoin constructPlan getOffer " + offer.getOffer().toString()); plan.n_stages = repetitions; plan.constructRepetitions(offer, fee_each_repetition, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { objectSuccessCallback.onSuccess(obj); } @Override public void onFail(String error) { _error(plan, error, objectSuccessCallback); } }); } catch (Exception e) { _error(plan, e.getLocalizedMessage(), objectSuccessCallback); e.printStackTrace(); } } //TODO call in same places as in js private void _error(Plan plan, String error, ObjectSuccessCallback objectSuccessCallback) { if (plan.generated_addresses == null) return; for (String key : plan.generated_addresses) { Log.d("SharedCoin", "SharedCoin _error deleteAddress " + key); //MyWallet.deleteAddress(key); //remoteWallet.removeKey(key); } objectSuccessCallback.onFail(error); } private static int[] divideUniformlyRandomly(final int sum, final int n) { int[] nums = new int[n]; long upperbound = Math.round(sum * 1.0 / n); long offset = Math.round(0.5 * upperbound); double cursum = 0; for (int i = 0; i < n; i++) { double rand = Math.floor((Math.random() * upperbound) + offset); if (cursum + rand > sum || i == n - 1) { rand = sum - cursum; } cursum += rand; nums[i] = (int) rand; if (cursum == sum) { break; } } return nums; } private BigInteger calculateFeeForValue(final BigInteger input_value) { BigInteger minFee = BigInteger.valueOf(getMinimumFee()); BigDecimal feePercent = new BigDecimal(getFeePercent()); //Log.d("SharedCoin", "SharedCoin calculateFeeForValue: minFee" + minFee); //Log.d("SharedCoin", "SharedCoin calculateFeeForValue: feePercent" + feePercent); if (input_value.compareTo(BigInteger.ZERO) > 0 && feePercent.intValue() > 0) { int mod = (int) Math.ceil(100 / feePercent.doubleValue()); BigInteger fee = input_value.divide(BigInteger.valueOf(mod)); if (minFee.compareTo(fee) > 0) { return minFee; } else { return fee; } } else { return minFee; } } private void complete(final Offer offer, final String tx_hash, final String tx, final ObjectSuccessCallback objectSuccessCallback) { Log.d("SharedCoin", "SharedCoin complete tx_hash: " + tx_hash); offer.determineOutputsToOfferNextStage(tx, new ObjectSuccessCallback() { @SuppressWarnings("unchecked") @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage onSuccess"); JSONArray outpoints_to_offer_next_stage = (JSONArray) obj; Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage onSuccess outpoints_to_offer_next_stage.size() " + outpoints_to_offer_next_stage.size()); for (int i = 0; i < outpoints_to_offer_next_stage.size(); i ++) { Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage onSuccess i " + i); JSONObject outpoint_to_offer_next_stage = (JSONObject) outpoints_to_offer_next_stage.get(i); outpoint_to_offer_next_stage.put("hash", tx); } Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage onSuccess 2"); objectSuccessCallback.onSuccess(outpoints_to_offer_next_stage); } @Override public void onFail(String error) { Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage onFail " + error); objectSuccessCallback.onFail(error); } }); } /* public boolean isEnabled() { return true; } public Double getFeePercent() { return 0.0; } public Long getMaximumInputValue() { return 100000000000L; } public Long getMaximumOfferNumberOfInputs() { return 20L; } public Long getMaximumOfferNumberOfOutputs() { return 20L; } public Long getMaximumOutputValue() { return 5000000000L; } public Long getMinSupportedVersion() { return 2L; } public Long getMinimumFee() { return 50000L; } public Long getMinimumInputValue() { return 1000000L; } public Long getMinimumOutputValue() { return 1000000L; } public Long getMinimumOutputValueExcludeFee() { return 5460L; } public Long getRecommendedIterations() { return 4L; } public Long getRecommendedMaxIterations() { return 10L; } public Long getRecommendedMinIterations() { return 2L; } public String getToken() { return "e1n5RxXrCS/K67WPoD+2MQuAd7BHnKocxeXb3XShc8C6Vgp1P7Q0tm9NG6nhFcHv33BKFTuxk4mJ8vVlRlx1t11qtRsbv43yCBQ4kL+O4TmiljvpdL/TSlw2pbO27vRf"; } //*/ //* public boolean isEnabled() { return info != null ? (Boolean) info.get("enabled") : false; } public Double getFeePercent() { return info != null ? (Double) info.get("fee_percent") : null; } public Long getMaximumInputValue() { return info != null ? (Long) info.get("maximum_input_value") : null; } public Long getMaximumOfferNumberOfInputs() { return info != null ? (Long) info.get("maximum_offer_number_of_inputs") : null; } public Long getMaximumOfferNumberOfOutputs() { return info != null ? (Long) info.get("maximum_offer_number_of_outputs") : null; } public Long getMaximumOutputValue() { return info != null ? (Long) info.get("maximum_output_value") : null; } public Long getMinSupportedVersion() { return info != null ? (Long) info.get("min_supported_version") : null; } public Long getMinimumFee() { return info != null ? (Long) info.get("minimum_fee") : null; } public Long getMinimumInputValue() { return info != null ? (Long) info.get("minimum_output_value") : null; } public Long getMinimumOutputValue() { return info != null ? (Long) info.get("minimum_output_value") : null; } public Long getMinimumOutputValueExcludeFee() { return info != null ? (Long) info.get("minimum_output_value_exclude_fee") : null; } public Long getRecommendedIterations() { return info != null ? (Long) info.get("recommended_iterations") : null; } public Long getRecommendedMaxIterations() { return info != null ? (Long) info.get("recommended_max_iterations") : null; } public Long getRecommendedMinIterations() { return info != null ? (Long) info.get("recommended_min_iterations") : null; } public String getToken() { return info != null ? (String) info.get("token") : null; } //*/ private class Offer { private long offer_id; //A unique ID for this offer (set by server) private JSONArray offered_outpoints; private JSONArray request_outputs; private SharedCoin sharedCoin; public Offer(SharedCoin sharedCoin) { this.sharedCoin = sharedCoin; this.offered_outpoints = new JSONArray(); this.request_outputs = new JSONArray(); this.offer_id = 0; } @SuppressWarnings("unchecked") public void addOfferedOutpoint(String hash, long index, String value) { JSONObject dict = new JSONObject(); dict.put("hash", hash); dict.put("index", index); dict.put("value", value); this.offered_outpoints.add(dict); } @SuppressWarnings("unchecked") public void addRequestOutputs(String value, String script) { JSONObject dict = new JSONObject(); dict.put("value", value); dict.put("script", script); this.request_outputs.add(dict); } @SuppressWarnings("unchecked") public void addRequestOutputs(String value, String script, boolean exclude_from_fee) { JSONObject dict = new JSONObject(); dict.put("value", value); dict.put("script", script); dict.put("exclude_from_fee", new Boolean(exclude_from_fee)); //dict.put("exclude_from_fee", exclude_from_fee); this.request_outputs.add(dict); } public void setOfferID(long id) { this.offer_id = id; } public long getOfferID() { return this.offer_id; } public JSONArray getOfferedOutpoints() { return this.offered_outpoints; } public JSONArray getRequestOutputs() { return this.request_outputs; } public void setOfferedOutpoints(Object object) { this.offered_outpoints = (JSONArray) object; } public void clearOfferedOutpoints() { this.offered_outpoints.clear(); } @SuppressWarnings("unchecked") public JSONObject getOffer() { JSONObject offerObject = new JSONObject(); offerObject.put("offered_outpoints", this.offered_outpoints); offerObject.put("request_outputs", this.request_outputs); offerObject.put("offer_id", this.offer_id); return offerObject; } private boolean isOutpointOneWeOffered(TransactionInput input) { try { byte[] bytes = input.getOutpoint().getHash().getBytes(); String hexHash = new String(Hex.encode(bytes)); long inputIndex = input.getOutpoint().getIndex(); Log.d("SharedCoin", "SharedCoin isOutpointOneWeOffered hexHash: " + hexHash); Log.d("SharedCoin", "SharedCoin isOutpointOneWeOffered inputIndex: " + inputIndex); for (int i = 0; i < this.offered_outpoints.size(); i++) { JSONObject request_outpoint = (JSONObject) this.offered_outpoints.get(i); String hash = (String) request_outpoint.get("hash"); long index = SharedCoin.getLongFromLong(request_outpoint, "index"); if (hash.equals(hexHash) && index == inputIndex) { return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } private boolean isOutputOneWeRequested(TransactionOutput output) { //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested ----------------------------------- "); try { BitcoinScript toOutputScript = new BitcoinScript(output.getScriptBytes()); byte[] program = toOutputScript.getProgram(); String scriptHex = new String(Hex.encode(program)); BigInteger value = output.getValue(); //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested self.request_outputs: " + this.request_outputs.toString()); //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested scriptHex: " + scriptHex); //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested value: " + value); for (int i = 0; i < this.request_outputs.size(); i++) { JSONObject request_output = (JSONObject) this.request_outputs.get(i); String script = (String) request_output.get("script"); String valueStr = SharedCoin.getIntegerStringFromIntegerString(request_output, "value"); //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested TransactionOutput script: " + script); //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested TransactionOutput value: " + valueStr); if (script.equals(scriptHex) && valueStr.equals(value.toString())) { //Log.d("SharedCoin", "SharedCoin sOutputOneWeRequested return true: "); return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } private boolean isOutputChange(TransactionOutput output) { try { BitcoinScript toOutputScript = new BitcoinScript(output.getScriptBytes()); byte[] program = toOutputScript.getProgram(); String scriptHex = new String(Hex.encode(program)); BigInteger value = output.getValue(); Log.d("SharedCoin", "SharedCoin isOutputChange self.request_outputs: " + this.request_outputs.toString()); Log.d("SharedCoin", "SharedCoin isOutputChange scriptHex: " + scriptHex); Log.d("SharedCoin", "SharedCoin isOutputChange value: " + value); for (int i = 0; i < this.request_outputs.size(); i++) { JSONObject request_output = (JSONObject) this.request_outputs.get(i); String script = (String) request_output.get("script"); String valueStr = SharedCoin.getIntegerStringFromIntegerString(request_output, "value"); if (script.equals(scriptHex) && valueStr.equals(value.toString())) { return request_output.get("exclude_from_fee") == null ? false : (Boolean) request_output.get("exclude_from_fee"); } } } catch (Exception e) { e.printStackTrace(); } return false; } @SuppressWarnings("unchecked") public void determineOutputsToOfferNextStage(String tx_hex, ObjectSuccessCallback objectSuccessCallback) { try { Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage "); Transaction tx = new Transaction(this.sharedCoin.params, Hex.decode(tx_hex.getBytes()), 0, null, false, true, Message.UNKNOWN_LENGTH); List<TransactionOutput> transactionOutputs = tx.getOutputs(); JSONArray outpoints_to_offer_next_stage = new JSONArray(); Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage transactionOutputs.size() " + transactionOutputs.size()); for (int i = 0; i < transactionOutputs.size(); i++) { TransactionOutput output = transactionOutputs.get(i); Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage i " + i); if (isOutputOneWeRequested(output)) { if (isOutputChange(output)) { JSONObject dict = new JSONObject(); dict.put("hash", null); dict.put("index", (long) i); dict.put("value", output.getValue().toString()); outpoints_to_offer_next_stage.add(dict); } } } Log.d("SharedCoin", "SharedCoin determineOutputsToOfferNextStage outpoints_to_offer_next_stage.size " + outpoints_to_offer_next_stage.size()); objectSuccessCallback.onSuccess(outpoints_to_offer_next_stage); } catch (ProtocolException e) { objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } } public void checkProposal(JSONObject proposal, ObjectSuccessCallback objectSuccessCallback) { try { Log.d("SharedCoin", "SharedCoin checkProposal proposal" + proposal.toString()); if (proposal.get("tx") == null) { throw new Exception("Proposal Transaction Is Null"); } String tx_hex = (String) proposal.get("tx"); Transaction tx = new Transaction(this.sharedCoin.params, Hex.decode(tx_hex.getBytes())); int output_matches = 0; List<TransactionOutput> transactionOutputs = tx.getOutputs(); Log.d("SharedCoin", "SharedCoin checkProposal transactionOutputs.size " + transactionOutputs.size()); for (Iterator<TransactionOutput> ito = transactionOutputs.iterator(); ito.hasNext();) { TransactionOutput output = ito.next(); if (this.isOutputOneWeRequested(output)) { ++output_matches; } } Log.d("SharedCoin", "SharedCoin checkProposal output_matches " + output_matches); Log.d("SharedCoin", "SharedCoin checkProposal this.request_outputs.size() " + this.request_outputs.size()); if (output_matches < this.request_outputs.size()) { throw new Exception("Could not find all our requested outputs (" + output_matches + " < " + this.request_outputs.size() + ")"); } int input_matches = 0; JSONArray signatureRequests = (JSONArray) proposal.get("signature_requests"); Log.d("SharedCoin", "SharedCoin checkProposal signatureRequests.size " + signatureRequests.size()); for (int i = 0; i < signatureRequests.size(); ++i) { JSONObject signatureRequest = (JSONObject) signatureRequests.get(i); BigInteger tx_index = SharedCoin.getBigIntegerFromLong(signatureRequest, "tx_input_index"); if (this.isOutpointOneWeOffered(tx.getInput(tx_index.intValue()))) { ++input_matches; } } if (this.offered_outpoints.size() != input_matches) { throw new Exception("Could not find all our offered outpoints " + this.offered_outpoints.size() + " != " + input_matches + ")"); } Log.d("SharedCoin", "SharedCoin checkProposal onSuccess"); objectSuccessCallback.onSuccess(tx); } catch (ProtocolException e) { Log.d("SharedCoin", "SharedCoin checkProposal ProtocolException" + e.getLocalizedMessage()); objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } catch (Exception e) { Log.d("SharedCoin", "SharedCoin checkProposal Exception" + e.getLocalizedMessage()); objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } } public void submit(SharedCoinSuccessCallback objectSuccessCallback) { try { JSONObject obj = SharedCoin.submitOffer(getOffer(), getToken(), null, null); String status = (String) obj.get("status"); Log.d("SharedCoin", "SharedCoin submit obj " + obj.toString()); if (status != null && status.equals(STATUS_COMPLETE)) { objectSuccessCallback.onComplete((String)obj.get("tx_hash"), (String)obj.get("tx")); } else if (obj.get("offer_id") == null) { objectSuccessCallback.onFail("Null offer_id returned"); } else { this.offer_id = SharedCoin.getLongFromLong(obj, "offer_id"); objectSuccessCallback.onSuccess(obj); } } catch (Exception e) { e.printStackTrace(); } } private JSONObject _pollForProposalID() { return SharedCoin.getOfferID(getOfferID()); } public void pollForProposalID(ObjectSuccessCallback objectSuccessCallback) { while (true) { Log.d("SharedCoin", "SharedCoin pollForProposalID"); JSONObject obj = this._pollForProposalID(); if (obj == null) { Log.d("SharedCoin", "SharedCoin pollForProposalID _pollForProposalID error obj == null"); break; } String status = (String) obj.get("status"); Log.d("SharedCoin", "SharedCoin pollForProposalID status " + status); if (status.equals(STATUS_WAITING)) { continue; } else if (status.equals(STATUS_NOT_FOUND)) { objectSuccessCallback.onFail("Offer ID Not Found"); break; } else if (status.equals(STATUS_ACTIVE_PROPOSAL)) { BigInteger proposalID = SharedCoin.getBigIntegerFromLong(obj, "proposal_id"); objectSuccessCallback.onSuccess(proposalID); break; } else { objectSuccessCallback.onFail("Unknown status " + status); break; } } } public void getProposal(BigInteger proposal_id, final SharedCoinSuccessCallback sharedCoinSuccessCallback) { SharedCoin.getProposalID(proposal_id, this.offer_id, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin getProposal " + obj.toString()); JSONObject jsonObject = (JSONObject)obj; String status = (String) jsonObject.get("status"); Log.d("SharedCoin", "SharedCoin getProposal status " + status); if (status.equals(STATUS_NOT_FOUND)) { sharedCoinSuccessCallback.onFail("Proposal or Offer ID Not Found"); } else if (status.equals(STATUS_COMPLETE)) { String txHash = (String) jsonObject.get("tx_hash"); String tx = (String) jsonObject.get("tx"); sharedCoinSuccessCallback.onComplete(txHash, tx); } else if (status.equals(STATUS_SIGNATURES_NEEDED)) { sharedCoinSuccessCallback.onSuccess(jsonObject); } else { Log.d("SharedCoin", "SharedCoin getProposal invalid status " + status); } } @Override public void onFail(String error) { Log.d("SharedCoin", "SharedCoin getProposalID error " + error); sharedCoinSuccessCallback.onFail(error); } }); } @SuppressWarnings("unchecked") public void signInputs(JSONObject proposal, Transaction tx, final ObjectSuccessCallback objectSuccessCallback) { Log.d("SharedCoin", "SharedCoin signInputs ----------------------------------- "); Log.d("SharedCoin", "SharedCoin signInputs proposal " + proposal.toString()); try { HashMap<String,String> tmp_cache = new HashMap<String,String>(); JSONArray connected_scripts = new JSONArray(); JSONArray signatureRequests = (JSONArray) proposal.get("signature_requests"); for (int i = 0; i < signatureRequests.size(); ++i) { JSONObject request = (JSONObject) signatureRequests.get(i); String tmp = (String) request.get("connected_script"); BitcoinScript connected_script = new BitcoinScript(Hex.decode(tmp.getBytes())); // if (connected_script == null) // throw new Exception("signInputs() Connected script is null"); JSONObject connectedScriptStuff = new JSONObject(); /* connectedScriptStuff.put("connected_script", connected_script); //*/ //* connectedScriptStuff.put("connected_script_hex", tmp); //*/ long tx_index = SharedCoin.getLongFromLong(request, "tx_input_index"); long offer_outpoint_index = SharedCoin.getLongFromLong(request, "offer_outpoint_index"); connectedScriptStuff.put("tx_input_index", tx_index); connectedScriptStuff.put("offer_outpoint_index", offer_outpoint_index); String inputAddress = connected_script.getAddress().toString(); Log.d("SharedCoin", "SharedCoin signInputs inputAddress " + inputAddress); if (tmp_cache.containsKey(inputAddress)) { Log.d("SharedCoin", "SharedCoin signInputs extra_private_keys key " + tmp_cache.get(inputAddress)); connectedScriptStuff.put("priv_to_use", tmp_cache.get(inputAddress)); } else if (sharedCoin.extra_private_keys.containsKey(inputAddress)) { Log.d("SharedCoin", "SharedCoin signInputs extra_private_keys key " + sharedCoin.extra_private_keys.get(inputAddress)); connectedScriptStuff.put("priv_to_use", sharedCoin.extra_private_keys.get(inputAddress)); } else if (Offer.this.sharedCoin.remoteWallet.isMine(inputAddress) && ! Offer.this.sharedCoin.remoteWallet.isWatchOnly(inputAddress)) { Log.d("SharedCoin", "SharedCoin signInputs remoteWallet.getPrivateKey key " + Offer.this.sharedCoin.remoteWallet.getPrivateKey(inputAddress)); connectedScriptStuff.put("priv_to_use", Offer.this.sharedCoin.remoteWallet.getPrivateKey(inputAddress)); } if (! connectedScriptStuff.containsKey("priv_to_use")) { throw new Exception("Private key not found"); } else { tmp_cache.put(inputAddress, (String) connectedScriptStuff.get("priv_to_use")); } Log.d("SharedCoin", "SharedCoin signInputs connectedScriptStuff " + connectedScriptStuff.toString()); // different from Javascript version connected_scripts is an array of Bitcoin.Script while java version is a JSONArray of JSONObject connected_scripts.add(connectedScriptStuff); } this.signNormal(tx, connected_scripts, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { JSONArray signatures = (JSONArray) obj; objectSuccessCallback.onSuccess(signatures); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } }); } catch (Exception e) { e.printStackTrace(); } } @SuppressWarnings("unchecked") private void signNormal(Transaction tx, JSONArray connected_scripts, ObjectSuccessCallback objectSuccessCallback) { Log.d("SharedCoin", "SharedCoin signNormal connected_scripts " + connected_scripts.toString()); JSONArray signatures = new JSONArray(); try { for (int index = 0; index < connected_scripts.size(); index++) { SharedCoin.setTimeout(1); JSONObject connectedScriptStuff = (JSONObject) connected_scripts.get(index); /* BitcoinScript connected_script = (BitcoinScript) connectedScriptStuff.get("connected_script"); if (connected_script == null) throw new Exception("Null connected script"); //*/ //* String connected_script_hex = (String) connectedScriptStuff.get("connected_script_hex"); BitcoinScript connected_script = new BitcoinScript(Hex.decode(connected_script_hex.getBytes())); //*/ BigInteger tx_input_index = SharedCoin.getBigIntegerFromLong(connectedScriptStuff, "tx_input_index"); String base58PrivKey = (String) connectedScriptStuff.get("priv_to_use"); byte[] signed_script = SharedCoin.signInput(this.sharedCoin.params, tx, tx_input_index.intValue(), base58PrivKey, connected_script, SigHash.ALL); if (signed_script != null) { JSONObject signature = new JSONObject(); signature.put("tx_input_index", connectedScriptStuff.get("tx_input_index")); String signedScriptHex = new String(Hex.encode(signed_script)); signature.put("input_script", signedScriptHex); signature.put("offer_outpoint_index", connectedScriptStuff.get("offer_outpoint_index")); signatures.add(signature); } else { throw new Exception("Unknown error signing transaction"); } } Log.d("SharedCoin", "SharedCoin signNormal signatures " + signatures.toString()); objectSuccessCallback.onSuccess(signatures); } catch (Exception e) { objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } } public void submitInputScripts(JSONObject proposal, JSONArray input_scripts, final SharedCoinSuccessCallback sharedCoinSuccessCallback) { sharedCoin.setLastSignatureSubmitTime(new Date().getTime()); Log.d("SharedCoin", "SharedCoin submit_signatures input_scripts " + input_scripts.toString()); Log.d("SharedCoin", "SharedCoin submit_signatures offer_id " + this.offer_id); Log.d("SharedCoin", "SharedCoin submit_signatures proposal " + proposal.toString()); long proposalID = SharedCoin.getLongFromLong(proposal, "proposal_id"); Log.d("SharedCoin", "SharedCoin submit_signatures proposal_id " + proposalID); SharedCoin.submitSignatures(proposalID, this.offer_id, input_scripts, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { JSONObject jsonObject = (JSONObject)obj; String status = (String) jsonObject.get("status"); Log.d("SharedCoin", "SharedCoin submitInputScripts status " + status); if (status.equals(STATUS_NOT_FOUND)) { sharedCoinSuccessCallback.onFail("Proposal Expired or Not Found"); } else if (status.equals(STATUS_VERIFICATION_FAILED)) { sharedCoinSuccessCallback.onFail("Signature Verification Failed"); } else if (status.equals(STATUS_COMPLETE)) { String txHash = (String) jsonObject.get("tx_hash"); String tx = (String) jsonObject.get("tx"); sharedCoinSuccessCallback.onComplete(txHash, tx); } else if (status.equals(STATUS_SIGNATURES_ACCEPTED)) { sharedCoinSuccessCallback.onSuccess(jsonObject); } else { sharedCoinSuccessCallback.onFail("Unknown status " + status); } } @Override public void onFail(String error) { sharedCoinSuccessCallback.onFail(error); } }); } } public class Plan { public int n_stages = 0; public int c_stage = 0; public String address_seed = null; public int address_seen_n = 0; public List<String> generated_addresses = null; List<Offer> offers = new ArrayList<Offer>(); public SharedCoin sharedCoin; public Plan(SharedCoin sharedCoin) { this.sharedCoin = sharedCoin; this.generated_addresses = new ArrayList<String>(); } @SuppressLint("TrulyRandom") public String generateAddressFromSeed() { if (this.address_seed == null) { SecureRandom random = new SecureRandom(); byte randomBytes[] = new byte[18]; random.nextBytes(randomBytes); this.address_seed = new String(Hex.encode(randomBytes)); Log.d("SharedCoin", "SharedCoin enerateAddressFromSeed1: " + SharedCoin.SEED_PREFIX + this.address_seed); List<String> additional_seeds = remoteWallet.getAdditionalSeeds(); for (String additional_seed : additional_seeds) Log.d("SharedCoin", "SharedCoin Saved Wallet additional_seed " + additional_seed); Log.d("SharedCoin", "SharedCoin enerateAddressFromSeed additional_seeds.size()b4 " + additional_seeds.size()); //wallet sync is called b4 execStage 0 remoteWallet.addAdditionalSeeds(new String(SharedCoin.SEED_PREFIX + this.address_seed)); } Log.d("SharedCoin", "SharedCoin enerateAddressFromSeed2: " + SharedCoin.SEED_PREFIX + this.address_seed); String address = generateAddressFromCustomSeed(SharedCoin.SEED_PREFIX+this.address_seed, this.address_seen_n); address_seen_n++; return address; } public String generateChangeAddress() { /* final ECKey key = remoteWallet.generateECKey(); final String address = key.toAddress(this.sharedCoin.params).toString(); this.generated_addresses.add(address); Log.d("SharedCoin", "SharedCoin generateChangeAddress: address: " + address); return address; //*/ //* final ECKey key = remoteWallet.generateECKey(); final String address = key.toAddress(this.sharedCoin.params).toString(); this.generated_addresses.add(address); Log.d("SharedCoin", "SharedCoin generateChangeAddress addKeyToWallet " + address); application.addKeyToWallet(key, address, null, 0, new AddAddressCallback() { public void onSavedAddress(String address) { Log.d("SharedCoin", "SharedCoin addKeyToWallet onSavedAddress "); } public void onError(String reason) { Log.d("SharedCoin", "SharedCoin addKeyToWallet onError "); } }); return address; //*/ } public void constructRepetitions(Offer initial_offer, BigInteger[] fee_each_repetition, ObjectSuccessCallback objectSuccessCallback) { try { Log.d("SharedCoin", "SharedCoin constructRepetitions initial_offer " + initial_offer.getOffer().toString()); Log.d("SharedCoin", "SharedCoin constructRepetitions fee_each_repetition " + Arrays.asList(fee_each_repetition)); BigInteger totalValueInput = BigInteger.ZERO; JSONArray offered_outpoints = initial_offer.getOfferedOutpoints(); for(int i = 0; i < offered_outpoints.size(); i++) { JSONObject dict = (JSONObject) offered_outpoints.get(i); totalValueInput = totalValueInput.add(SharedCoin.getIntegerFromIntegerString(dict, "value")); } BigInteger totalValueLeftToConsume = totalValueInput; BigInteger totalChangeValueLeftToConsume = BigInteger.ZERO; for (int ii = 0; ii < this.n_stages-1; ++ii) { Offer offer = new Offer(this.sharedCoin); //Copy the inputs from the last offer if (ii == 0) { JSONArray request_outputs = initial_offer.getRequestOutputs(); for(int i = 0; i < request_outputs.size(); i++) { JSONObject dict = (JSONObject) request_outputs.get(i); if (dict.containsKey("exclude_from_fee")) { request_outputs.remove(i); totalChangeValueLeftToConsume = SharedCoin.getIntegerFromIntegerString(dict, "value"); totalValueLeftToConsume = totalValueLeftToConsume.subtract(totalChangeValueLeftToConsume); break; } } //offer.offered_outpoints = initial_offer.offered_outpoints.slice(0); offer.setOfferedOutpoints(offered_outpoints.clone()); initial_offer.clearOfferedOutpoints(); } totalValueLeftToConsume = totalValueLeftToConsume.subtract(fee_each_repetition[ii]); final double splitValues[] = new double[] {10, 5, 1, 0.5, 0.3, 0.1}; final int maxSplits = 8; SecureRandom random = new SecureRandom(); double rand = random.nextDouble(); int minSplits; if (totalValueLeftToConsume.intValue() >= 0.2*SATOSHI) { minSplits = 2; if (rand >= 0.5) { minSplits = 3; } } else { minSplits = 1; } BigInteger changeValue = BigInteger.ZERO; int changePercent = 100; if (totalChangeValueLeftToConsume.compareTo(BigInteger.ZERO) < 0) { throw new Exception("totalChangeValueLeftToConsume < 0"); } else if (totalChangeValueLeftToConsume.compareTo(BigInteger.ZERO) > 0) { changeValue = totalChangeValueLeftToConsume.divide(BigInteger.valueOf(100)).multiply(BigInteger.valueOf(changePercent)); } Log.d("SharedCoin", "SharedCoin changeValue " + changeValue.toString()); if (changeValue.compareTo(BigInteger.valueOf(getMinimumOutputValue())) <= 0 || totalChangeValueLeftToConsume.subtract(changeValue).compareTo(BigInteger.valueOf(getMinimumOutputValue())) <= 0) { changeValue = totalChangeValueLeftToConsume; totalChangeValueLeftToConsume = BigInteger.ZERO; } else { totalChangeValueLeftToConsume = totalChangeValueLeftToConsume.subtract(changeValue); } Log.d("SharedCoin", "SharedCoin totalChangeValueLeftToConsume " + totalChangeValueLeftToConsume); if (totalChangeValueLeftToConsume.compareTo(BigInteger.ZERO) < 0) { throw new Exception("totalChangeValueLeftToConsume < 0"); } BigInteger totalValue = totalValueLeftToConsume.add(totalChangeValueLeftToConsume); boolean outputsAdded = false; for (int _i = 0; _i < 1000; ++_i) { for (int j = 0; j < splitValues.length; j++) { int sK = j; //Log.d("SharedCoin", "SharedCoin constructRepetitions sK " + sK); double randDouble = random.nextDouble(); //Log.d("SharedCoin", "SharedCoin constructRepetitions randDouble " + randDouble); //Log.d("SharedCoin", "SharedCoin constructRepetitions splitValues[sK] " + splitValues[sK]); double variance = (splitValues[sK] / 100) * ((randDouble*30)-15); //Log.d("SharedCoin", "SharedCoin constructRepetitions variance " + variance); long tmp = Math.round((splitValues[sK] + variance) * SATOSHI); BigInteger splitValue = new BigInteger(Long.toString(tmp)); //Log.d("SharedCoin", "SharedCoin constructRepetitions totalValue " + totalValue); //Log.d("SharedCoin", "SharedCoin constructRepetitions splitValue " + splitValue); BigInteger[] valueAndRemainder = totalValue.divideAndRemainder(splitValue); //Log.d("SharedCoin", "SharedCoin constructRepetitions valueAndRemainder0 " + valueAndRemainder[0]); //Log.d("SharedCoin", "SharedCoin constructRepetitions valueAndRemainder1 " + valueAndRemainder[1]); int quotient = valueAndRemainder[0].intValue(); if (new BigInteger(Integer.toString(quotient)).compareTo(BigInteger.valueOf(getMaximumOfferNumberOfOutputs())) > 0) { //if (quotient > getMaximumOfferNumberOfOutputs()) { continue; } //Log.d("SharedCoin", "SharedCoin quotient " + quotient); //Log.d("SharedCoin", "SharedCoin minSplits " + minSplits); //Log.d("SharedCoin", "SharedCoin maxSplits " + maxSplits); if (quotient >= minSplits && quotient <= maxSplits) { if (valueAndRemainder[1].compareTo(BigInteger.ZERO) == 0 || valueAndRemainder[1].compareTo(BigInteger.valueOf(getMinimumOutputValue())) >= 0) { int[] remainderDivides = null; if (valueAndRemainder[1].compareTo(BigInteger.ZERO) > 0) { if (quotient <= 1) { if (valueAndRemainder[1].compareTo(BigInteger.valueOf(getMinimumInputValue())) < 0 || valueAndRemainder[1].compareTo(BigInteger.valueOf(getMaximumOutputValue())) > 0) { continue; } String new_address = this.generateAddressFromSeed(); Log.d("SharedCoin", "SharedCoin constructRepetitions new_address " + new_address); BitcoinScript bitcoinScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(new_address)); String scriptHex = new String(Hex.encode(bitcoinScript.getProgram())); offer.addRequestOutputs(valueAndRemainder[1].toString(), scriptHex); } else { Log.d("SharedCoin", "SharedCoin remainderDivides " + Arrays.asList(remainderDivides)); remainderDivides = divideUniformlyRandomly(valueAndRemainder[1].intValue(), quotient); } } Log.d("SharedCoin", "SharedCoin remainderDivides " + Arrays.asList(remainderDivides)); boolean withinRange = true; for (int iii = 0; iii < quotient; ++iii) { BigInteger value = splitValue; if (remainderDivides != null && remainderDivides.length > iii) { if (remainderDivides[iii] > 0) { value = value.add(BigInteger.valueOf(remainderDivides[iii])); } } if (value.compareTo(BigInteger.valueOf(getMinimumInputValue())) < 0 || value.compareTo(BigInteger.valueOf(getMaximumOutputValue())) > 0) { withinRange = false; break; } } if (!withinRange) { continue; } for (int iii = 0; iii < quotient; ++iii) { String new_address = this.generateAddressFromSeed(); BigInteger value = splitValue; if (remainderDivides != null && remainderDivides.length > iii) { if (remainderDivides[iii] > 0) { value = value.add(BigInteger.valueOf(remainderDivides[iii])); } } BitcoinScript bitcoinScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(new_address)); String scriptHex = new String(Hex.encode(bitcoinScript.getProgram())); offer.addRequestOutputs(value.toString(), scriptHex); } outputsAdded = true; break; } } } if (outputsAdded) break; } Log.d("SharedCoin", "SharedCoin constructRepetitions 6 "); if (!outputsAdded) { String new_address = this.generateAddressFromSeed(); BitcoinScript bitcoinScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(new_address)); String scriptHex = new String(Hex.encode(bitcoinScript.getProgram())); offer.addRequestOutputs(totalValue.toString(), scriptHex); } Log.d("SharedCoin", "SharedCoin constructRepetitions 7 "); if (changeValue.compareTo(BigInteger.ZERO) > 0) { String change_address = generateChangeAddress(); if (changeValue.compareTo(BigInteger.valueOf(getMinimumOutputValueExcludeFee())) < 0) throw new Exception("Change Value Too Small 0 (" + changeValue.toString() + "< " + getMinimumOutputValueExcludeFee() + ")"); BitcoinScript bitcoinScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(change_address)); String scriptHex = new String(Hex.encode(bitcoinScript.getProgram())); offer.addRequestOutputs(changeValue.toString(), scriptHex, true); } Log.d("SharedCoin", "SharedCoin constructRepetitions 8 "); this.offers.add(offer); } Log.d("SharedCoin", "SharedCoin totalChangeValueLeftToConsume " + totalChangeValueLeftToConsume); if (totalChangeValueLeftToConsume.compareTo(BigInteger.ZERO) > 0) { String change_address = generateChangeAddress(); if (totalChangeValueLeftToConsume.compareTo(BigInteger.valueOf(getMinimumOutputValueExcludeFee())) < 0) throw new Exception("Change Value Too Small 1 (" + totalChangeValueLeftToConsume.toString() + " < " + getMinimumOutputValueExcludeFee()+ ")"); BitcoinScript bitcoinScript = BitcoinScript.createSimpleOutBitoinScript(new BitcoinAddress(change_address)); String scriptHex = new String(Hex.encode(bitcoinScript.getProgram())); initial_offer.addRequestOutputs(totalChangeValueLeftToConsume.toString(), scriptHex, true); } this.offers.add(initial_offer); Log.d("SharedCoin", "SharedCoin this.offers.size() " + this.offers.size()); if (SHARED_COIN_DEBUG) { this.offers.clear(); Offer offer1 = new Offer(this.sharedCoin); offer1.setOfferID(0L); offer1.addOfferedOutpoint("6e659f6cb8a160c55e65d589c85bdbaa9cd0b6bfb2e68939f5ad68ffa6fe9c84", 0, "12507377"); offer1.addRequestOutputs("1380521", "76a914d363c62f93406334cd8aadae68f85d104d04e8c788ac"); offer1.addRequestOutputs("8669479", "76a914998484121e9ea5af4fef79f5b55d036156f5e43e88ac"); offer1.addRequestOutputs("2407377", "76a914931070cb85ecdc8ae1150d4a1128decd493cd49588ac", true); this.offers.add(offer1); Offer offer2 = new Offer(this.sharedCoin); offer2.setOfferID(0L); offer2.addRequestOutputs("10000000", "76a91472ef9da834b88115510dd35e808f9e0d0c67e9c488ac"); this.offers.add(offer2); } //* //debug code Log.d("SharedCoin", "SharedCoin initial_offer " + initial_offer.getOffer().toString()); for (Offer offer : this.offers) { Log.d("SharedCoin", "SharedCoin self.offers " + offer.getOffer().toString()); JSONArray getRequestOutputs = offer.getRequestOutputs(); BigInteger sum1 = BigInteger.ZERO; for (int i = 0; i < getRequestOutputs.size(); i ++) { JSONObject obj = (JSONObject) getRequestOutputs.get(i); BigInteger value = SharedCoin.getIntegerFromIntegerString(obj, "value"); Log.d("SharedCoin", "SharedCoin RequestOutput value " + obj.get("value")); sum1 = sum1.add(value); } sum1 = sum1.add(fee_each_repetition[0]); BigInteger sum2 = BigInteger.ZERO; JSONArray getOfferedOutpoints = offer.getOfferedOutpoints(); for (int i = 0; i < getOfferedOutpoints.size(); i ++) { JSONObject obj = (JSONObject) getOfferedOutpoints.get(i); BigInteger value = SharedCoin.getIntegerFromIntegerString(obj, "value"); Log.d("SharedCoin", "SharedCoin OfferedOutpoint value " + obj.get("value")); sum2 = sum2.add(value); } Log.d("SharedCoin", "SharedCoin sum1 " + sum1); Log.d("SharedCoin", "SharedCoin sum2 " + sum2); } //*/ objectSuccessCallback.onSuccess(this); } catch (Exception e) { Log.d("SharedCoin", "SharedCoin constructRepetitions Exception e " + e.getLocalizedMessage()); objectSuccessCallback.onFail(e.getLocalizedMessage()); } } private void _success(int ii, JSONArray outpoints_to_offer_next_stage, final ObjectSuccessCallback objectSuccessCallback) { ii++; Log.d("SharedCoin", "SharedCoin Executing Stage_success ii " + ii); Log.d("SharedCoin", "SharedCoin Executing Stage_success this.n_stages " + this.n_stages); Log.d("SharedCoin", "SharedCoin Executing Stage_success this.c_stage " + this.c_stage); if (ii < this.n_stages) { //Connect the outputs created from the previous stage to the inputs to use this stage this.offers.get(ii).setOfferedOutpoints(outpoints_to_offer_next_stage); Log.d("SharedCoin", "SharedCoin Executing Stage _success outpoints_to_offer_next_stage: " + outpoints_to_offer_next_stage.toString()); Log.d("SharedCoin", "SharedCoin Executing Stage _success self.offers: " + this.offers.toArray().toString()); execStage(ii, objectSuccessCallback); } else if (ii == this.n_stages) { Log.d("SharedCoin", "SharedCoin Executing Stage _success ii == self.n_stages"); objectSuccessCallback.onSuccess(null); } } public void execStage(final int ii, final ObjectSuccessCallback objectSuccessCallback) { this.c_stage = ii; final Offer offerForThisStage = this.offers.get(ii); Log.d("SharedCoin", "SharedCoin Executing Stage " + ii); this.executeOffer(offerForThisStage, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { JSONArray outpoints_to_offer_next_stage = (JSONArray) obj; _success(ii, outpoints_to_offer_next_stage, objectSuccessCallback); } @Override public void onFail(final String error) { Log.d("SharedCoin", "SharedCoin executeOffer onFail " + error); SharedCoin.setTimeout(5000); Log.d("SharedCoin", "SharedCoin executeOffer failed " + error); Plan.this.executeOffer(offerForThisStage, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { JSONArray outpoints_to_offer_next_stage = (JSONArray) obj; _success(ii, outpoints_to_offer_next_stage, objectSuccessCallback); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } }); } }); } public void execute(final ObjectSuccessCallback objectSuccessCallback) { List<String> additional_seeds = remoteWallet.getAdditionalSeeds(); Log.d("SharedCoin", "SharedCoin execute additional_seeds.size() " + additional_seeds.size()); final String seed = SharedCoin.SEED_PREFIX + this.address_seed; //double check to see if see seed exist in payload, it can fail to exist if payload gets reset b4 seed is synced. if (! additional_seeds.contains(seed)) { Log.d("SharedCoin", "SharedCoin execute check to add seed "); remoteWallet.addAdditionalSeeds(seed); } //debug code, use to clear seed list so recoverSeeds is shorter, dont actually use in production //remoteWallet.clearAdditionalSeeds(); this.sharedCoin.application.saveWallet(new SuccessCallback() { @Override public void onSuccess() { EventListeners.invokeWalletDidChange(); Log.d("SharedCoin", "SharedCoin Saved Wallet "); List<String> additional_seeds = remoteWallet.getAdditionalSeeds(); for (String additional_seed : additional_seeds) Log.d("SharedCoin", "SharedCoin Saved Wallet additional_seed " + additional_seed); Log.d("SharedCoin", "SharedCoin Saved Wallet additional_seeds.size() " + additional_seeds.size()); //Log.d("SharedCoin", "SharedCoin Saved Wallet additional_seeds " + additional_seeds.toString()); Log.d("SharedCoin", "SharedCoin Saved Wallet Plan.this.address_seed " + SharedCoin.SEED_PREFIX + Plan.this.address_seed); boolean found = false; if (additional_seeds.contains(SharedCoin.SEED_PREFIX + Plan.this.address_seed)) { Log.d("SharedCoin", "SharedCoin Saved Wallet additional_seeds contains " + Plan.this.address_seed); found = true; } if (! found) { Log.d("SharedCoin", "SharedCoin Address Seed not found even after wallet sync"); objectSuccessCallback.onFail("Address Seed Not Found"); } else { Log.d("SharedCoin", "SharedCoin execStage "); execStage(0, objectSuccessCallback); } } @Override public void onFail() { objectSuccessCallback.onFail("Error saving wallet"); } }); } public void executeOffer(final Offer offer, final ObjectSuccessCallback objectSuccessCallback) { offer.submit(new SharedCoinSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin Successfully Submitted Offer"); offer.pollForProposalID(new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { final BigInteger proposalID = (BigInteger) obj; Log.d("SharedCoin", "SharedCoin Proposal ID " + proposalID); offer.getProposal(proposalID, new SharedCoinSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin Got Proposal"); final JSONObject proposal = (JSONObject)obj; offer.checkProposal(proposal, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin Proposal Looks Good"); final Transaction tx = (Transaction)obj; offer.signInputs(proposal, tx, new ObjectSuccessCallback() { @Override public void onSuccess(Object obj) { Log.d("SharedCoin", "SharedCoin Inputs Signed"); final JSONArray signatures = (JSONArray)obj; offer.submitInputScripts(proposal, signatures, new SharedCoinSuccessCallback() { @Override public void onSuccess( Object obj) { Log.d("SharedCoin", "SharedCoin Submitted Input Scripts"); pollForCompleted(proposalID, new SharedCoinSuccessCallback() { @Override public void onSuccess( Object obj) { Log.d("SharedCoin", "SharedCoin pollForCompleted onSuccess"); } @Override public void onFail( String error) { Log.d("SharedCoin", "SharedCoin pollForCompleted onFail " + error); objectSuccessCallback.onFail(error); } @Override public void onComplete( String txHash, String tx) { Log.d("SharedCoin", "SharedCoin pollForCompleted onComplete " + txHash); complete(offer, txHash, tx, objectSuccessCallback); } }); } @Override public void onFail( String error) { objectSuccessCallback.onFail(error); } @Override public void onComplete( String txHash, String tx) { complete(offer, txHash, tx, objectSuccessCallback); } }); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } }); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } }); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } @Override public void onComplete(String txHash, String tx) { complete(offer, txHash, tx, objectSuccessCallback); } }); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } }); } @Override public void onFail(String error) { objectSuccessCallback.onFail(error); } @Override public void onComplete(String txHash, String tx) { complete(offer, txHash, tx, objectSuccessCallback); } }); } } private static JSONObject getOfferID(final long offerID) { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("version", VERSION); params.put("method", "get_offer_id"); params.put("offer_id", offerID); params.put("format", "json"); try { if (! SHARED_COIN_DEBUG) { String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); JSONObject obj = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request get_offer_id " + obj.toString()); return obj; } else { return SharedCoin.getOfferIDDebugReturnObject(); } } catch (Exception e) { Log.d("SharedCoin", "SharedCoin getOfferID Exception " + e.getLocalizedMessage()); e.printStackTrace(); } return null; } private static void getProposalID(final BigInteger proposalID, final long offerID, ObjectSuccessCallback objectSuccessCallback) { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("version", VERSION); params.put("method", "get_proposal_id"); params.put("proposal_id", proposalID.longValue()); params.put("offer_id", offerID); params.put("format", "json"); try { if (! SHARED_COIN_DEBUG) { String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); JSONObject obj = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request get_proposal_id " + obj.toString()); objectSuccessCallback.onSuccess(obj); } else { objectSuccessCallback.onSuccess(SharedCoin.getProposalIDDebugReturnObject()); } } catch (Exception e) { Log.d("SharedCoin", "SharedCoin getProposalID Exception " + e.getLocalizedMessage()); objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } } private static JSONObject pollForProposalCompleted(final BigInteger proposalID) { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("version", VERSION); params.put("method", "poll_for_proposal_completed"); params.put("proposal_id", proposalID.longValue()); params.put("format", "json"); try { if (! SHARED_COIN_DEBUG) { String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); JSONObject obj = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request poll_for_proposal_completed " + obj.toString()); return obj; } else { return SharedCoin.pollForProposalCompletedDebugReturnObject(); } } catch (Exception e) { Log.d("SharedCoin", "SharedCoin pollForProposalCompleted Exception " + e.getLocalizedMessage()); e.printStackTrace(); } return null; } private static JSONObject submitOffer(final JSONObject offer, final String token, final BigInteger feePercent, final String offerMaxAge) throws Exception { Map<Object, Object> params = new HashMap<Object, Object>(); Log.d("SharedCoin", "SharedCoin submitOffer offer.toString " + offer.toString()); Log.d("SharedCoin", "SharedCoin submitOffer token " + token); params.put("version", VERSION); params.put("method", "submit_offer"); params.put("token", token); params.put("format", "json"); //if (feePercent != null) params.put("fee_percent", feePercent); //if (offerMaxAge != null) params.put("offer_max_age", offerMaxAge); params.put("offer", offer); try { if (! SHARED_COIN_DEBUG) { String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); Log.d("SharedCoin", "SharedCoin request submit_offer response " + response); JSONObject obj = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request submit_offer " + obj.toString()); return obj; } else { return SharedCoin.submitOfferDebugReturnObject(); } } catch (Exception e) { Log.d("SharedCoin", "SharedCoin submitOffer Exception " + e.getLocalizedMessage()); e.printStackTrace(); } return null; } private static void submitSignatures(final long proposalID, final long offerID, final JSONArray inputScripts, ObjectSuccessCallback objectSuccessCallback) { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("version", VERSION); params.put("method", "submit_signatures"); params.put("proposal_id", proposalID); params.put("offer_id", offerID); params.put("input_scripts", inputScripts); params.put("format", "json"); try { if (! SHARED_COIN_DEBUG) { String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); JSONObject obj = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request submit_signatures " + obj.toString()); objectSuccessCallback.onSuccess(obj); } else { objectSuccessCallback.onSuccess(SharedCoin.submitSignaturesDebugReturnObject()); } } catch (Exception e) { Log.d("SharedCoin", "SharedCoin submitSignatures Exception " + e.getLocalizedMessage()); objectSuccessCallback.onFail(e.getLocalizedMessage()); e.printStackTrace(); } } public void getInfo() throws Exception { Map<Object, Object> params = new HashMap<Object, Object>(); params.put("version", VERSION); params.put("method", "get_info"); params.put("format", "json"); String response = WalletUtils.postURLWithParams(SHARED_COIN_ENDPOINT, params); info = (JSONObject) new JSONParser().parse(response); Log.d("SharedCoin", "SharedCoin request get_info " + info.toString()); } private static JSONObject getOfferIDDebugReturnObject() { int returnChoice = 1; JSONObject obj = new JSONObject(); if (returnChoice == 1) { long proposal_id = 98237649241957L; obj.put("proposal_id", proposal_id); obj.put("status", STATUS_ACTIVE_PROPOSAL); } else { obj.put("status", STATUS_WAITING); } return obj; } private static JSONObject getProposalIDDebugReturnObject() { String tx_hex = "010000000a15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0100000000ffffffff78396f7aaf464e7a6f55d0512c3d84528eaf3f4bddfae11df469ee87bd09a9032100000000ffffffff92642cb19c9452224ab2094df8f8db0b5bb5a09196f400a7dfa9f47ce99281260d00000000ffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0700000000ffffffff849cfea6ff68adf53989e6b2bfb6d09caadb5bc889d5655ec560a1b86c9f656e0000000000ffffffffade0e052ac3aae6440a836809381dfeabf76fbc01364a2a4ee0fabe0ae4afe6f0a00000000ffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0500000000ffffffff4e046d1a59f0fdd2c916942d73f8194b18935939ac3b5f1c56b00d59335a41021200000000ffffffff77ffce18ab121eda835b84a1b7206ac84bfabfc28a2330105f4125ac706f98ae0000000000ffffffff05e9f0a1c8a5e8181aacc5d1971d97b43e05eb099ba7336e8d8c78919d7b4e2d0400000000ffffffff1196556e00000000001976a91435071d5261f1b99c815d12009e4fa14d7fa2488388acb0094600000000001976a914490d274fb93434bb9b2367779b77b153eb9c442488ac20505300000000001976a914fe850333caeaadba1e6682ecc9b9bae4c7cfd1e588aca9ca6f00000000001976a914969c3d4c0d4de45faba213264de3979b95497a1888acb0094600000000001976a914e2b59bc7a23d1ddefcdc4031287d2706d816a33288ac208c4900000000001976a914a09c2e8fd6b2e69ac309ace730d6f901c4bc1fcc88ac208c4900000000001976a914c59f457c4715631a973b8fb888cb4a77134e5baf88acf84a4e00000000001976a91416812cd6ebf94bd0299f16ba4c8b2f74611bcefd88ac0ec04f00000000001976a9145378df64160ad1f879c1643e6c128b8bf3a2168288ac81c56a00000000001976a914cffc87b6bb211801a3b78a12f906b505c1fd74ec88ac27498400000000001976a914998484121e9ea5af4fef79f5b55d036156f5e43e88acf1476e00000000001976a914f14799f957c6a2eadac288a7967c1744f5db91a788acb0cd4f00000000001976a9143f4782d7a3aeb8b0dd5487e502bd0392ff9443ea88aca9101500000000001976a914d363c62f93406334cd8aadae68f85d104d04e8c788acd80e4b00000000001976a9142dd0e6f44019949806fe15b2e49c637a97fc9dba88ac76841d00000000001976a9148234791e754096891b76e2f616b936430b750e9588acd1bb2400000000001976a914931070cb85ecdc8ae1150d4a1128decd493cd49588ac00000000"; String connected_script = "76a9144b43401df52008c0076e4a9bb139aba9a72b365088ac"; long proposal_id = 98237649241957L; long tx_input_index = 4L; long offer_outpoint_index = 0L; JSONObject p = new JSONObject(); p.put("tx", tx_hex); p.put("proposal_id", proposal_id); p.put("status", STATUS_SIGNATURES_NEEDED); JSONObject signature = new JSONObject(); signature.put("connected_script", connected_script); signature.put("tx_input_index", tx_input_index); signature.put("offer_outpoint_index", offer_outpoint_index); JSONArray signature_requests = new JSONArray(); signature_requests.add(signature); p.put("signature_requests", signature_requests); return p; } private static JSONObject pollForProposalCompletedDebugReturnObject() { int returnChoice = 3; JSONObject obj = new JSONObject(); if (returnChoice == 1) { obj.put("status", STATUS_WAITING); } else if (returnChoice == 2) { obj.put("status", STATUS_NOT_FOUND); } else if (returnChoice == 3) { String tx = "010000000a15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0100000000ffffffff78396f7aaf464e7a6f55d0512c3d84528eaf3f4bddfae11df469ee87bd09a9032100000000ffffffff92642cb19c9452224ab2094df8f8db0b5bb5a09196f400a7dfa9f47ce99281260d00000000ffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0700000000ffffffff849cfea6ff68adf53989e6b2bfb6d09caadb5bc889d5655ec560a1b86c9f656e0000000000ffffffffade0e052ac3aae6440a836809381dfeabf76fbc01364a2a4ee0fabe0ae4afe6f0a00000000ffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d0500000000ffffffff4e046d1a59f0fdd2c916942d73f8194b18935939ac3b5f1c56b00d59335a41021200000000ffffffff77ffce18ab121eda835b84a1b7206ac84bfabfc28a2330105f4125ac706f98ae0000000000ffffffff05e9f0a1c8a5e8181aacc5d1971d97b43e05eb099ba7336e8d8c78919d7b4e2d0400000000ffffffff1196556e00000000001976a91435071d5261f1b99c815d12009e4fa14d7fa2488388acb0094600000000001976a914490d274fb93434bb9b2367779b77b153eb9c442488ac20505300000000001976a914fe850333caeaadba1e6682ecc9b9bae4c7cfd1e588aca9ca6f00000000001976a914969c3d4c0d4de45faba213264de3979b95497a1888acb0094600000000001976a914e2b59bc7a23d1ddefcdc4031287d2706d816a33288ac208c4900000000001976a914a09c2e8fd6b2e69ac309ace730d6f901c4bc1fcc88ac208c4900000000001976a914c59f457c4715631a973b8fb888cb4a77134e5baf88acf84a4e00000000001976a91416812cd6ebf94bd0299f16ba4c8b2f74611bcefd88ac0ec04f00000000001976a9145378df64160ad1f879c1643e6c128b8bf3a2168288ac81c56a00000000001976a914cffc87b6bb211801a3b78a12f906b505c1fd74ec88ac27498400000000001976a914998484121e9ea5af4fef79f5b55d036156f5e43e88acf1476e00000000001976a914f14799f957c6a2eadac288a7967c1744f5db91a788acb0cd4f00000000001976a9143f4782d7a3aeb8b0dd5487e502bd0392ff9443ea88aca9101500000000001976a914d363c62f93406334cd8aadae68f85d104d04e8c788acd80e4b00000000001976a9142dd0e6f44019949806fe15b2e49c637a97fc9dba88ac76841d00000000001976a9148234791e754096891b76e2f616b936430b750e9588acd1bb2400000000001976a914931070cb85ecdc8ae1150d4a1128decd493cd49588ac00000000"; String tx_hash = "6e659f6cb8a160c55e65d589c85bdbaa9cd0b6bfb2e68939f5ad68ffa6fe9c84"; obj.put("tx", tx); obj.put("tx_hash", tx_hash); obj.put("status", STATUS_COMPLETE); } else { obj.put("status", "unkown status test"); } return obj; } private static JSONObject submitOfferDebugReturnObject() { long offer_id = 85839170277513L; JSONObject obj = new JSONObject(); obj.put("offer_id", offer_id); return obj; } private static JSONObject submitSignaturesDebugReturnObject() { // int returnChoice = 3; int returnChoice = 4; JSONObject obj = new JSONObject(); if (returnChoice == 1) { obj.put("status", STATUS_NOT_FOUND); } else if (returnChoice == 2) { obj.put("status", STATUS_VERIFICATION_FAILED); } else if (returnChoice == 3) { String tx_hash = "62d668e2c4587808e6b68aa761d64190b4c57167f6979bed454a0aa16cee0d96"; String tx = "010000000a15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d010000006b483045022100b2f98f8d86efd4f47ee21ce64de531ef9961a9570d9406b87a3ab34474a75df402200b3503f6452973115ce92047a79cc4050d997e5fc4a3ec96bb05b88b2159227f0121030583fd5e8891a280b678c9f949d436766c723d5b14e93989feadb289992ac9f9ffffffff78396f7aaf464e7a6f55d0512c3d84528eaf3f4bddfae11df469ee87bd09a903210000008c493046022100d1a3dcf3f3b70777617dd2b64f448e90900766c080cdc488adb8356a0710d795022100cceab90ce2b7d50facdeba0fd07fc2cbe8822836351fe058874457b1238de3810141041b7acbadaa9ef5df75d0772d260de67db2f6432ada05ac758775b1c252e6b585f5c9a30783f75cf5e27e51067af034485849d37fea7a2e73e0dee478df1890a2ffffffff92642cb19c9452224ab2094df8f8db0b5bb5a09196f400a7dfa9f47ce99281260d0000008b4830450221008ef3aa6195e2e43583e46e1cf753583a192f665b8453cbccec7c4214bdfdf2a7022077b173d3639c37f1af6389512ea83e764e5c2fce4c741989eee60d71e033522b0141046f9c23459694d53445c4a4aa3131537347e5bd4080ad7a5de77e5d6b4246464eb74c2ccce9d07436fba2d0f4846c1aa6c7a89eb75d577bb24bdd6af334853f20ffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d070000008b48304502205acf3ba874d7a06d604cfcc6cdb64efa30f0fbe18fb0de54c608ea5fff4f418b022100bfffa4ee2f9f5f556dcf5fcdb188f9b717630893d43d74ecbdc3d5fcbc29c605014104eb6a9068f79818181f76a2b33b3d318ce5342deaf4aa1623cbcbec079945a4a495d2a60e8d6c875a29fbf405245368578fa833d7774daef25223ce300710cf0dffffffff849cfea6ff68adf53989e6b2bfb6d09caadb5bc889d5655ec560a1b86c9f656e000000008c493046022100b89a97ca74cfb4bd315a02c991d3491bfd7e50248f877fbced95712f22449643022100a65228bf6eed45895a309ec64e8e6e0f9d7e023c6779bd7e57239f8f2c6f94ee01410450eac3564aaf354e031721236eae4281ad9f36e2dc55c47249356cf347d01bc2ae945356e619994029c7a7d1876e7b424e6fa8b39b37a5259e3d976cd51cb798ffffffffade0e052ac3aae6440a836809381dfeabf76fbc01364a2a4ee0fabe0ae4afe6f0a0000006c49304602210096ba2fdcb2df9b15e7e33d7f2ea1cb8ce480935bd9e2220fc421850a25363e92022100b658bb2857393dfa8ab2fafe84a1c430f8b12b7c54801d8a29d844398f70a96501210223896a55142c179362e5d8039773265677371188e492a4367d9c11a1a5ff805fffffffff15714bbf9daf68562d5fa08bf0307fc4379393bf2c4c9e2a25b9612a6b478c7d05000000694630430220598ea8d6ef4ae6ea0c9d4a60f93daed4142787f01ac7f85979cdb8588b306ff8021f1eaefd2fe59ed5f35846654166cb63ddde211e9795ec2b3549cb680392262e01210212aeaca10ad318a08fa44e04e0c216ac7b5767bc75b4e24bffb4bc390f6fa57affffffff4e046d1a59f0fdd2c916942d73f8194b18935939ac3b5f1c56b00d59335a4102120000006a4730440220251e873c8cb3886d28727653022c46041973ad311413bc5f73a1533979514b30022062683914a5e5329622ea11f23a2d3d4becd505024203fda2449838eb1f6e49910121027f71bb43eeff86d934dc6a38ac1cc168b16f8295702d4da97d3f037223ae5b47ffffffff77ffce18ab121eda835b84a1b7206ac84bfabfc28a2330105f4125ac706f98ae000000008b483045022077bdab615964e2803cc1aa6b87d8c1b673da3703ee77c667fb8ea1534a594fe80221008c1214557b103bfe2dc2538c24e968ed36bbb44b49f5305d845a28e6736025d8014104e492997da7ad25a7a06cdc0a11bfccedef08d149de6a97bb2ab456ca305338bbba9d5986aa33ccd6670e4cbb7edd46b1ebbdaced18c30192d20812cbee01bf20ffffffff05e9f0a1c8a5e8181aacc5d1971d97b43e05eb099ba7336e8d8c78919d7b4e2d040000008b483045022044030a58a9974b41695ed71de9b9c688463ea4d595ca2b433862a4387d387d45022100eb6649762fa891aaa584eb6cc53bd5b6ed75ae63e57ee0ab1a2d1cc2174abcf5014104aaee7c7b62ad2f83d3a0b6c09f348090eba218958eed7910919ab019e0a7803d88b337fb9fa98febeee94698d5dc6c9c2b3dc2d5afba9a4197b6980cca35ffe3ffffffff1196556e00000000001976a91435071d5261f1b99c815d12009e4fa14d7fa2488388acb0094600000000001976a914490d274fb93434bb9b2367779b77b153eb9c442488ac20505300000000001976a914fe850333caeaadba1e6682ecc9b9bae4c7cfd1e588aca9ca6f00000000001976a914969c3d4c0d4de45faba213264de3979b95497a1888acb0094600000000001976a914e2b59bc7a23d1ddefcdc4031287d2706d816a33288ac208c4900000000001976a914a09c2e8fd6b2e69ac309ace730d6f901c4bc1fcc88ac208c4900000000001976a914c59f457c4715631a973b8fb888cb4a77134e5baf88acf84a4e00000000001976a91416812cd6ebf94bd0299f16ba4c8b2f74611bcefd88ac0ec04f00000000001976a9145378df64160ad1f879c1643e6c128b8bf3a2168288ac81c56a00000000001976a914cffc87b6bb211801a3b78a12f906b505c1fd74ec88ac27498400000000001976a914998484121e9ea5af4fef79f5b55d036156f5e43e88acf1476e00000000001976a914f14799f957c6a2eadac288a7967c1744f5db91a788acb0cd4f00000000001976a9143f4782d7a3aeb8b0dd5487e502bd0392ff9443ea88aca9101500000000001976a914d363c62f93406334cd8aadae68f85d104d04e8c788acd80e4b00000000001976a9142dd0e6f44019949806fe15b2e49c637a97fc9dba88ac76841d00000000001976a9148234791e754096891b76e2f616b936430b750e9588acd1bb2400000000001976a914931070cb85ecdc8ae1150d4a1128decd493cd49588ac00000000"; obj.put("status", STATUS_COMPLETE); obj.put("tx_hash", tx_hash); obj.put("tx", tx); } else if (returnChoice == 4) { obj.put("status", STATUS_SIGNATURES_ACCEPTED); } else { obj.put("status", "unkown status test"); } return obj; } }