Search in sources :

Example 26 with UTXO

use of com.samourai.wallet.send.UTXO in project samourai-wallet-android by Samourai-Wallet.

the class RicochetMeta method getHop0Tx.

private Transaction getHop0Tx(List<UTXO> utxos, long spendAmount, String destination, long fee, boolean samouraiFeeViaBIP47, long nTimeLock) {
    List<MyTransactionOutPoint> unspent = new ArrayList<MyTransactionOutPoint>();
    long totalValueSelected = 0L;
    for (UTXO u : utxos) {
        totalValueSelected += u.getValue();
        unspent.addAll(u.getOutpoints());
    }
    // Log.d("RicochetMeta", "spendAmount:" + spendAmount);
    // Log.d("RicochetMeta", "fee:" + fee);
    // Log.d("RicochetMeta", "totalValueSelected:" + totalValueSelected);
    BigInteger samouraiFeeAmount = samouraiFeeAmountV2;
    long changeAmount = totalValueSelected - (spendAmount + fee);
    // Log.d("RicochetMeta", "changeAmount:" + changeAmount);
    HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
    if (changeAmount > 0L) {
        String change_address = BIP84Util.getInstance(context).getAddressAt(AddressFactory.CHANGE_CHAIN, BIP84Util.getInstance(context).getWallet().getAccount(0).getChange().getAddrIdx()).getBech32AsString();
        receivers.put(change_address, BigInteger.valueOf(changeAmount));
    }
    if (samouraiFeeViaBIP47) {
        // Samourai fee paid in the hops
        receivers.put(destination, BigInteger.valueOf(spendAmount));
    } else {
        if (nTimeLock > 0L) {
            receivers.put(SamouraiWallet.getInstance().isTestNet() ? TESTNET_NLOCKTIME_SAMOURAI_RICOCHET_TX_FEE_ADDRESS : SAMOURAI_NLOCKTIME_RICOCHET_TX_FEE_ADDRESS, samouraiFeeAmount);
        } else {
            receivers.put(SamouraiWallet.getInstance().isTestNet() ? TESTNET_SAMOURAI_RICOCHET_TX_FEE_ADDRESS : SAMOURAI_RICOCHET_TX_FEE_ADDRESS, samouraiFeeAmount);
        }
        receivers.put(destination, BigInteger.valueOf(spendAmount - samouraiFeeAmount.longValue()));
    }
    Transaction tx = SendFactory.getInstance(context).makeTransaction(0, unspent, receivers);
    if (nTimeLock > 0L) {
        tx.setLockTime(nTimeLock);
    }
    tx = SendFactory.getInstance(context).signTransaction(tx, 0);
    return tx;
}
Also used : UTXO(com.samourai.wallet.send.UTXO) Transaction(org.bitcoinj.core.Transaction) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) BigInteger(java.math.BigInteger) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint)

Example 27 with UTXO

use of com.samourai.wallet.send.UTXO in project samourai-wallet-android by Samourai-Wallet.

the class RicochetMeta method script.

public JSONObject script(long spendAmount, long feePerKBAmount, String strDestination, int nbHops, String strPCode, boolean samouraiFeeViaBIP47, boolean useTimeLock) {
    JSONObject jObj = new JSONObject();
    try {
        BigInteger biSpend = BigInteger.valueOf(spendAmount);
        // default 4 hops min. for base fee, each additional hop 0.001
        BigInteger biSamouraiFee = BigInteger.valueOf(samouraiFeeAmountV2.longValue() * ((nbHops - defaultNbHops) + 1));
        BigInteger biFeePerKB = BigInteger.valueOf(feePerKBAmount);
        long latestBlock = APIFactory.getInstance(context).getLatestBlockHeight();
        long nTimeLock = 0L;
        if (useTimeLock && latestBlock > 0L) {
            nTimeLock = latestBlock;
        }
        jObj.put("ts", System.currentTimeMillis() / 1000L);
        jObj.put("hops", nbHops);
        jObj.put("spend_amount", biSpend.longValue());
        jObj.put("samourai_fee", biSamouraiFee.longValue());
        jObj.put("samourai_fee_via_bip47", samouraiFeeViaBIP47);
        jObj.put("feeKB", biFeePerKB.longValue());
        jObj.put("destination", strDestination);
        if (strPCode != null) {
            jObj.put("pcode", strPCode);
        }
        if (useTimeLock) {
            jObj.put("nTimeLock", nTimeLock);
        }
        JSONObject jHop = new JSONObject();
        JSONArray jHops = new JSONArray();
        int hopSz = 0;
        if (samouraiFeeViaBIP47) {
            hopSz = FeeUtil.getInstance().estimatedSize(1, 2);
        } else {
            hopSz = FeeUtil.getInstance().estimatedSize(1, 1);
        }
        BigInteger biFeePerHop = FeeUtil.getInstance().calculateFee(hopSz, biFeePerKB);
        Pair<List<UTXO>, BigInteger> pair = getHop0UTXO(spendAmount, nbHops, biFeePerHop.longValue(), samouraiFeeViaBIP47);
        List<UTXO> utxos = pair.getLeft();
        long totalValueSelected = 0L;
        for (UTXO u : utxos) {
            totalValueSelected += u.getValue();
        }
        // Log.d("RicochetMeta", "totalValueSelected (return):" + totalValueSelected);
        // hop0 'leaves' wallet, change returned to wallet
        BigInteger hop0 = biSpend.add(biSamouraiFee).add(biFeePerHop.multiply(BigInteger.valueOf((long) nbHops)));
        // BigInteger hop0Fee = FeeUtil.getInstance().calculateFee(hop0sz, biFeePerKB);
        BigInteger hop0Fee = pair.getRight();
        // Log.d("RicochetMeta", "hop0Fee (return):" + hop0Fee.longValue());
        Transaction txHop0 = getHop0Tx(utxos, hop0.longValue(), getDestinationAddress(index), hop0Fee.longValue(), samouraiFeeViaBIP47, nTimeLock);
        if (txHop0 == null) {
            return null;
        }
        // Log.d("RicochetMeta", "searching for:" + getDestinationAddress(index));
        int prevTxN = 0;
        for (int i = 0; i < txHop0.getOutputs().size(); i++) {
            Script script = txHop0.getOutputs().get(i).getScriptPubKey();
            // Log.d("RicochetMeta", "script:" + Hex.toHexString(script.getProgram()));
            String address = null;
            if (Hex.toHexString(script.getProgram()).startsWith("0014")) {
                String hrp = null;
                if (SamouraiWallet.getInstance().getCurrentNetworkParams() instanceof TestNet3Params) {
                    hrp = "tb";
                } else {
                    hrp = "bc";
                }
                try {
                    String _script = Hex.toHexString(script.getProgram());
                    address = Bech32Segwit.encode(hrp, (byte) 0x00, Hex.decode(_script.substring(4).getBytes()));
                } catch (Exception e) {
                    ;
                }
            // Log.d("RicochetMeta", "bech32:" + address);
            } else {
                address = new Script(script.getProgram()).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
            // Log.d("RicochetMeta", "address from script:" + address);
            }
            if (address.equals(getDestinationAddress(index))) {
                prevTxN = i;
                // Log.d("RicochetMeta", "tx output n:" + prevTxN);
                break;
            }
        }
        jHop.put("seq", 0);
        jHop.put("spend_amount", hop0.longValue());
        jHop.put("fee", hop0Fee.longValue());
        jHop.put("fee_per_hop", biFeePerHop.longValue());
        jHop.put("index", index);
        jHop.put("destination", getDestinationAddress(index));
        // Log.d("RicochetMeta", "destination:" + getDestinationAddress(index));
        int prevIndex = index;
        index++;
        jHop.put("tx", new String(Hex.encode(txHop0.bitcoinSerialize())));
        jHop.put("hash", txHop0.getHash().toString());
        if (useTimeLock) {
            jHop.put("nTimeLock", nTimeLock);
        }
        jHops.put(jHop);
        List<Pair<String, Long>> samouraiFees = new ArrayList<Pair<String, Long>>();
        if (samouraiFeeViaBIP47) {
            long baseVal = samouraiFeeAmountV2.longValue() / 4L;
            long totalVal = 0L;
            SecureRandom random = new SecureRandom();
            int _outgoingIdx = BIP47Meta.getInstance().getOutgoingIdx(BIP47Meta.strSamouraiDonationPCode);
            for (int i = 0; i < 4; i++) {
                int val = random.nextInt(25000);
                int sign = random.nextInt(1);
                if (sign == 0) {
                    val *= -1L;
                }
                long feeVal = 0L;
                if (i == 3) {
                    feeVal = samouraiFeeAmountV2.longValue() - totalVal;
                } else {
                    feeVal = baseVal + val;
                    totalVal += feeVal;
                }
                // 
                try {
                    PaymentCode pcode = new PaymentCode(BIP47Meta.strSamouraiDonationPCode);
                    PaymentAddress paymentAddress = BIP47Util.getInstance(context).getSendAddress(pcode, _outgoingIdx + i);
                    // String strAddress = paymentAddress.getSendECKey().toAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    // 
                    // derive as bech32
                    // 
                    SegwitAddress segwitAddress = new SegwitAddress(paymentAddress.getSendECKey().getPubKey(), SamouraiWallet.getInstance().getCurrentNetworkParams());
                    String strAddress = segwitAddress.getBech32AsString();
                    samouraiFees.add(Pair.of(strAddress, feeVal));
                // samouraiFees.add(Pair.of(strAddress, 200000L / 4L));
                } catch (Exception e) {
                    samouraiFees.add(Pair.of(SendNotifTxFactory.SAMOURAI_NOTIF_TX_FEE_ADDRESS, feeVal));
                }
            }
        }
        Transaction txHop = null;
        String prevTxHash = txHop0.getHash().toString();
        String prevScriptPubKey = Hex.toHexString(txHop0.getOutput(prevTxN).getScriptPubKey().getProgram());
        BigInteger remainingSamouraiFee = BigInteger.ZERO;
        long prevSpendValue = hop0.longValue();
        if (!samouraiFeeViaBIP47) {
            prevSpendValue -= biSamouraiFee.longValue();
        } else {
            remainingSamouraiFee = samouraiFeeAmountV2;
        }
        int _hop = 0;
        for (int i = (nbHops - 1); i >= 0; i--) {
            _hop++;
            BigInteger hopx = null;
            if (samouraiFeeViaBIP47) {
                remainingSamouraiFee = remainingSamouraiFee.subtract(BigInteger.valueOf(samouraiFees.get(_hop - 1).getRight()));
                hopx = biSpend.add(biFeePerHop.multiply(BigInteger.valueOf((long) i))).add(remainingSamouraiFee);
            } else {
                hopx = biSpend.add(biFeePerHop.multiply(BigInteger.valueOf((long) i)));
            }
            if (useTimeLock && latestBlock > 0L) {
                nTimeLock = latestBlock + _hop;
            }
            // Log.d("RicochetMeta", "doing hop:" + _hop);
            if (samouraiFeeViaBIP47 && ((_hop - 1) < 4)) {
                txHop = getHopTx(prevTxHash, prevTxN, prevIndex, prevSpendValue, hopx.longValue(), _hop < nbHops ? getDestinationAddress(index) : strDestination, samouraiFees.get(_hop - 1), nTimeLock);
            } else {
                txHop = getHopTx(prevTxHash, prevTxN, prevIndex, prevSpendValue, hopx.longValue(), _hop < nbHops ? getDestinationAddress(index) : strDestination, null, nTimeLock);
            }
            if (txHop == null) {
                return null;
            }
            jHop = new JSONObject();
            jHop.put("seq", (nbHops - i));
            jHop.put("spend_amount", hopx.longValue());
            jHop.put("fee", biFeePerHop.longValue());
            jHop.put("prev_tx_hash", prevTxHash);
            jHop.put("prev_tx_n", prevTxN);
            jHop.put("prev_spend_value", prevSpendValue);
            jHop.put("script", prevScriptPubKey);
            jHop.put("tx", new String(Hex.encode(txHop.bitcoinSerialize())));
            jHop.put("hash", txHop.getHash().toString());
            if (useTimeLock) {
                jHop.put("nTimeLock", nTimeLock);
            }
            if (_hop < nbHops) {
                jHop.put("index", index);
                jHop.put("destination", getDestinationAddress(index));
                // Log.d("RicochetMeta", "destination:" + getDestinationAddress(index));
                prevIndex = index;
                index++;
            } else {
                jHop.put("destination", strDestination);
            // Log.d("RicochetMeta", "destination:" + strDestination);
            }
            if (samouraiFeeViaBIP47) {
                jObj.put("samourai_fee_address", samouraiFees.get(_hop - 1).getLeft());
                jObj.put("samourai_fee_amount", samouraiFees.get(_hop - 1).getRight());
            }
            jHops.put(jHop);
            prevTxHash = txHop.getHash().toString();
            prevTxN = 0;
            prevSpendValue = hopx.longValue();
            prevScriptPubKey = Hex.toHexString(txHop.getOutputs().get(0).getScriptPubKey().getProgram());
        }
        jObj.put("hops", jHops);
        BigInteger totalAmount = hop0.add(hop0Fee);
        jObj.put("total_spend", totalAmount.longValue());
    } catch (JSONException je) {
        return null;
    }
    System.out.println("RicochetMeta:" + jObj.toString());
    return jObj;
}
Also used : Script(org.bitcoinj.script.Script) PaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode) SegwitAddress(com.samourai.wallet.segwit.SegwitAddress) JSONArray(org.json.JSONArray) ArrayList(java.util.ArrayList) SecureRandom(java.security.SecureRandom) JSONException(org.json.JSONException) PaymentAddress(com.samourai.wallet.bip47.rpc.PaymentAddress) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) JSONException(org.json.JSONException) ScriptException(org.bitcoinj.script.ScriptException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) UTXO(com.samourai.wallet.send.UTXO) TestNet3Params(org.bitcoinj.params.TestNet3Params) JSONObject(org.json.JSONObject) Transaction(org.bitcoinj.core.Transaction) BigInteger(java.math.BigInteger) List(java.util.List) ArrayList(java.util.ArrayList) LinkedList(java.util.LinkedList) Pair(org.apache.commons.lang3.tuple.Pair)

Example 28 with UTXO

use of com.samourai.wallet.send.UTXO in project samourai-wallet-android by Samourai-Wallet.

the class APIFactory method parseUnspentOutputs.

private synchronized boolean parseUnspentOutputs(String unspents) {
    if (unspents != null) {
        try {
            JSONObject jsonObj = new JSONObject(unspents);
            if (jsonObj == null || !jsonObj.has("unspent_outputs")) {
                return false;
            }
            JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
            if (utxoArray == null || utxoArray.length() == 0) {
                return false;
            }
            for (int i = 0; i < utxoArray.length(); i++) {
                JSONObject outDict = utxoArray.getJSONObject(i);
                byte[] hashBytes = Hex.decode((String) outDict.get("tx_hash"));
                Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                int txOutputN = ((Number) outDict.get("tx_output_n")).intValue();
                BigInteger value = BigInteger.valueOf(((Number) outDict.get("value")).longValue());
                String script = (String) outDict.get("script");
                byte[] scriptBytes = Hex.decode(script);
                int confirmations = ((Number) outDict.get("confirmations")).intValue();
                String path = null;
                try {
                    String address = null;
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        address = Bech32Util.getInstance().getAddressFromScript(script);
                    } else {
                        address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    }
                    if (outDict.has("xpub")) {
                        JSONObject xpubObj = (JSONObject) outDict.get("xpub");
                        path = (String) xpubObj.get("path");
                        String m = (String) xpubObj.get("m");
                        unspentPaths.put(address, path);
                        if (m.equals(BIP49Util.getInstance(context).getWallet().getAccount(0).xpubstr())) {
                            // assume account 0
                            unspentBIP49.put(address, 0);
                        } else if (m.equals(BIP84Util.getInstance(context).getWallet().getAccount(0).xpubstr())) {
                            // assume account 0
                            unspentBIP84.put(address, 0);
                        } else {
                            unspentAccounts.put(address, AddressFactory.getInstance(context).xpub2account().get(m));
                        }
                    } else if (outDict.has("pubkey")) {
                        int idx = BIP47Meta.getInstance().getIdx4AddrLookup().get(outDict.getString("pubkey"));
                        BIP47Meta.getInstance().getIdx4AddrLookup().put(address, idx);
                        String pcode = BIP47Meta.getInstance().getPCode4AddrLookup().get(outDict.getString("pubkey"));
                        BIP47Meta.getInstance().getPCode4AddrLookup().put(address, pcode);
                        debug("APIFactory", outDict.getString("pubkey") + "," + pcode);
                        debug("APIFactory", outDict.getString("pubkey") + "," + idx);
                    } else {
                        ;
                    }
                    // Construct the output
                    MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                    outPoint.setConfirmations(confirmations);
                    if (utxos.containsKey(script)) {
                        utxos.get(script).getOutpoints().add(outPoint);
                    } else {
                        UTXO utxo = new UTXO();
                        utxo.getOutpoints().add(outPoint);
                        utxo.setPath(path);
                        utxos.put(script, utxo);
                    }
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        UTXOFactory.getInstance().addP2WPKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    } else if (Address.fromBase58(SamouraiWallet.getInstance().getCurrentNetworkParams(), address).isP2SHAddress()) {
                        UTXOFactory.getInstance().addP2SH_P2WPKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    } else {
                        UTXOFactory.getInstance().addP2PKH(txHash.toString(), txOutputN, script, utxos.get(script));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            long amount = 0L;
            for (String key : utxos.keySet()) {
                for (MyTransactionOutPoint out : utxos.get(key).getOutpoints()) {
                    debug("APIFactory", "utxo:" + out.getAddress() + "," + out.getValue());
                    debug("APIFactory", "utxo:" + utxos.get(key).getPath());
                    amount += out.getValue().longValue();
                }
            }
            debug("APIFactory", "utxos by value (post-parse):" + amount);
            return true;
        } catch (JSONException je) {
            ;
        }
    }
    return false;
}
Also used : Script(org.bitcoinj.script.Script) Sha256Hash(org.bitcoinj.core.Sha256Hash) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) NotSecp256k1Exception(com.samourai.wallet.bip47.rpc.NotSecp256k1Exception) JSONException(org.json.JSONException) AddressFormatException(org.bitcoinj.core.AddressFormatException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) InvalidKeyException(java.security.InvalidKeyException) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) MnemonicException(org.bitcoinj.crypto.MnemonicException) DecryptionException(com.samourai.wallet.crypto.DecryptionException) IOException(java.io.IOException) NoSuchProviderException(java.security.NoSuchProviderException) UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) JSONObject(org.json.JSONObject) BigInteger(java.math.BigInteger)

Example 29 with UTXO

use of com.samourai.wallet.send.UTXO in project samourai-wallet-android by Samourai-Wallet.

the class APIFactory method parseUnspentOutputsForSweep.

private synchronized UTXO parseUnspentOutputsForSweep(String unspents) {
    UTXO utxo = null;
    if (unspents != null) {
        try {
            JSONObject jsonObj = new JSONObject(unspents);
            if (jsonObj == null || !jsonObj.has("unspent_outputs")) {
                return null;
            }
            JSONArray utxoArray = jsonObj.getJSONArray("unspent_outputs");
            if (utxoArray == null || utxoArray.length() == 0) {
                return null;
            }
            for (int i = 0; i < utxoArray.length(); i++) {
                JSONObject outDict = utxoArray.getJSONObject(i);
                byte[] hashBytes = Hex.decode((String) outDict.get("tx_hash"));
                Sha256Hash txHash = Sha256Hash.wrap(hashBytes);
                int txOutputN = ((Number) outDict.get("tx_output_n")).intValue();
                BigInteger value = BigInteger.valueOf(((Number) outDict.get("value")).longValue());
                String script = (String) outDict.get("script");
                byte[] scriptBytes = Hex.decode(script);
                int confirmations = ((Number) outDict.get("confirmations")).intValue();
                try {
                    String address = null;
                    if (Bech32Util.getInstance().isBech32Script(script)) {
                        address = Bech32Util.getInstance().getAddressFromScript(script);
                        debug("address parsed:", address);
                    } else {
                        address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                    }
                    // Construct the output
                    MyTransactionOutPoint outPoint = new MyTransactionOutPoint(txHash, txOutputN, value, scriptBytes, address);
                    outPoint.setConfirmations(confirmations);
                    if (utxo == null) {
                        utxo = new UTXO();
                    }
                    utxo.getOutpoints().add(outPoint);
                } catch (Exception e) {
                    ;
                }
            }
        } catch (JSONException je) {
            ;
        }
    }
    return utxo;
}
Also used : Script(org.bitcoinj.script.Script) Sha256Hash(org.bitcoinj.core.Sha256Hash) JSONArray(org.json.JSONArray) JSONException(org.json.JSONException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) NotSecp256k1Exception(com.samourai.wallet.bip47.rpc.NotSecp256k1Exception) JSONException(org.json.JSONException) AddressFormatException(org.bitcoinj.core.AddressFormatException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) InvalidKeyException(java.security.InvalidKeyException) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) MnemonicException(org.bitcoinj.crypto.MnemonicException) DecryptionException(com.samourai.wallet.crypto.DecryptionException) IOException(java.io.IOException) NoSuchProviderException(java.security.NoSuchProviderException) UTXO(com.samourai.wallet.send.UTXO) BlockedUTXO(com.samourai.wallet.send.BlockedUTXO) JSONObject(org.json.JSONObject) BigInteger(java.math.BigInteger)

Example 30 with UTXO

use of com.samourai.wallet.send.UTXO in project samourai-wallet-android by Samourai-Wallet.

the class CahootsUtil method doSTONEWALLx2_2.

// 
// sender
// 
public Cahoots doSTONEWALLx2_2(STONEWALLx2 stonewall1) throws Exception {
    Transaction transaction = stonewall1.getTransaction();
    debug("CahootsUtil", "step2 tx:" + org.spongycastle.util.encoders.Hex.toHexString(transaction.bitcoinSerialize()));
    int nbIncomingInputs = transaction.getInputs().size();
    List<UTXO> utxos = getCahootsUTXO(stonewall1.getAccount());
    Collections.shuffle(utxos);
    debug("CahootsUtil", "BIP84 utxos:" + utxos.size());
    List<String> seenTxs = new ArrayList<String>();
    for (TransactionInput input : transaction.getInputs()) {
        if (!seenTxs.contains(input.getOutpoint().getHash().toString())) {
            seenTxs.add(input.getOutpoint().getHash().toString());
        }
    }
    List<UTXO> selectedUTXO = new ArrayList<UTXO>();
    long totalSelectedAmount = 0L;
    int nbTotalSelectedOutPoints = 0;
    for (int step = 0; step < 3; step++) {
        if (stonewall1.getCounterpartyAccount() == 0) {
            step = 2;
        }
        List<String> _seenTxs = seenTxs;
        selectedUTXO = new ArrayList<UTXO>();
        nbTotalSelectedOutPoints = 0;
        for (UTXO utxo : utxos) {
            switch(step) {
                case 0:
                    if (utxo.getPath() != null && utxo.getPath().length() > 3 && utxo.getPath().charAt(2) != '0') {
                        continue;
                    }
                    break;
                case 1:
                    if (utxo.getPath() != null && utxo.getPath().length() > 3 && utxo.getPath().charAt(2) != '1') {
                        continue;
                    }
                    break;
                default:
                    break;
            }
            UTXO _utxo = new UTXO();
            for (MyTransactionOutPoint outpoint : utxo.getOutpoints()) {
                if (!_seenTxs.contains(outpoint.getTxHash().toString())) {
                    _utxo.getOutpoints().add(outpoint);
                    _seenTxs.add(outpoint.getTxHash().toString());
                }
            }
            if (_utxo.getOutpoints().size() > 0) {
                selectedUTXO.add(_utxo);
                totalSelectedAmount += _utxo.getValue();
                nbTotalSelectedOutPoints += _utxo.getOutpoints().size();
                debug("CahootsUtil", "BIP84 selected utxo:" + _utxo.getValue());
            }
            if (totalSelectedAmount > FeeUtil.getInstance().estimatedFeeSegwit(0, 0, nbTotalSelectedOutPoints + nbIncomingInputs, 4).longValue() + stonewall1.getSpendAmount() + SamouraiWallet.bDust.longValue()) {
                break;
            }
        }
        if (totalSelectedAmount > FeeUtil.getInstance().estimatedFeeSegwit(0, 0, nbTotalSelectedOutPoints + nbIncomingInputs, 4).longValue() + stonewall1.getSpendAmount() + SamouraiWallet.bDust.longValue()) {
            break;
        }
    }
    if (!(totalSelectedAmount > FeeUtil.getInstance().estimatedFeeSegwit(0, 0, nbTotalSelectedOutPoints + nbIncomingInputs, 4).longValue() + stonewall1.getSpendAmount() + SamouraiWallet.bDust.longValue())) {
        return null;
    }
    debug("CahootsUtil", "BIP84 selected utxos:" + selectedUTXO.size());
    long fee = FeeUtil.getInstance().estimatedFeeSegwit(0, 0, nbTotalSelectedOutPoints + nbIncomingInputs, 4).longValue();
    debug("CahootsUtil", "fee:" + fee);
    if (fee % 2L != 0) {
        fee++;
    }
    debug("CahootsUtil", "fee pair:" + fee);
    stonewall1.setFeeAmount(fee);
    debug("CahootsUtil", "destination:" + stonewall1.getDestination());
    if (transaction.getOutputs() != null && transaction.getOutputs().size() == 2) {
        int idx = -1;
        for (int i = 0; i < 2; i++) {
            byte[] buf = transaction.getOutputs().get(i).getScriptBytes();
            byte[] script = new byte[buf.length];
            script[0] = 0x00;
            System.arraycopy(buf, 1, script, 1, script.length - 1);
            debug("CahootsUtil", "script:" + new Script(script).toString());
            debug("CahootsUtil", "script hex:" + Hex.toHexString(script));
            debug("CahootsUtil", "address from script:" + Bech32Util.getInstance().getAddressFromScript(new Script(script)));
            if (Bech32Util.getInstance().getAddressFromScript(new Script(script)).equalsIgnoreCase(stonewall1.getCollabChange())) {
                idx = i;
                break;
            }
        }
        if (idx == 0 || idx == 1) {
            Coin value = transaction.getOutputs().get(idx).getValue();
            Coin _value = Coin.valueOf(value.longValue() - (fee / 2L));
            debug("CahootsUtil", "output value post fee:" + _value);
            transaction.getOutputs().get(idx).setValue(_value);
            stonewall1.getPSBT().setTransaction(transaction);
        } else {
            return null;
        }
    } else {
        return null;
    }
    NetworkParameters params = stonewall1.getParams();
    // 
    // 
    // step2: B verif, utxos -> A (take smallest that cover amount)
    // 
    // 
    String zpub = BIP84Util.getInstance(context).getWallet().getAccountAt(stonewall1.getAccount()).zpubstr();
    HashMap<_TransactionOutPoint, Triple<byte[], byte[], String>> inputsB = new HashMap<_TransactionOutPoint, Triple<byte[], byte[], String>>();
    for (UTXO utxo : selectedUTXO) {
        for (MyTransactionOutPoint outpoint : utxo.getOutpoints()) {
            _TransactionOutPoint _outpoint = new _TransactionOutPoint(outpoint);
            ECKey eckey = SendFactory.getPrivKey(_outpoint.getAddress(), stonewall1.getAccount());
            String path = APIFactory.getInstance(context).getUnspentPaths().get(_outpoint.getAddress());
            inputsB.put(_outpoint, Triple.of(eckey.getPubKey(), FormatsUtil.getInstance().getFingerprintFromXPUB(zpub), path));
        }
    }
    // spender change output
    HashMap<_TransactionOutput, Triple<byte[], byte[], String>> outputsB = new HashMap<_TransactionOutput, Triple<byte[], byte[], String>>();
    if (stonewall1.getAccount() == WhirlpoolMeta.getInstance(context).getWhirlpoolPostmix()) {
        int idx = AddressFactory.getInstance(context).getHighestPostChangeIdx();
        SegwitAddress segwitAddress = BIP84Util.getInstance(context).getAddressAt(stonewall1.getAccount(), 1, idx);
        Pair<Byte, byte[]> pair0 = Bech32Segwit.decode(SamouraiWallet.getInstance().isTestNet() ? "tb" : "bc", segwitAddress.getBech32AsString());
        byte[] scriptPubKey_B0 = Bech32Segwit.getScriptPubkey(pair0.getLeft(), pair0.getRight());
        _TransactionOutput output_B0 = new _TransactionOutput(params, null, Coin.valueOf((totalSelectedAmount - stonewall1.getSpendAmount()) - (fee / 2L)), scriptPubKey_B0);
        outputsB.put(output_B0, Triple.of(segwitAddress.getECKey().getPubKey(), stonewall1.getFingerprint(), "M/1/" + idx));
    } else {
        int idx = BIP84Util.getInstance(context).getWallet().getAccount(0).getChange().getAddrIdx();
        SegwitAddress segwitAddress = BIP84Util.getInstance(context).getAddressAt(0, 1, idx);
        Pair<Byte, byte[]> pair0 = Bech32Segwit.decode(SamouraiWallet.getInstance().isTestNet() ? "tb" : "bc", segwitAddress.getBech32AsString());
        byte[] scriptPubKey_B0 = Bech32Segwit.getScriptPubkey(pair0.getLeft(), pair0.getRight());
        _TransactionOutput output_B0 = new _TransactionOutput(params, null, Coin.valueOf((totalSelectedAmount - stonewall1.getSpendAmount()) - (fee / 2L)), scriptPubKey_B0);
        outputsB.put(output_B0, Triple.of(segwitAddress.getECKey().getPubKey(), stonewall1.getFingerprint(), "M/1/" + idx));
    }
    STONEWALLx2 stonewall2 = new STONEWALLx2(stonewall1);
    stonewall2.inc(inputsB, outputsB, null);
    return stonewall2;
}
Also used : HashMap(java.util.HashMap) SegwitAddress(com.samourai.wallet.segwit.SegwitAddress) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) ECKey(org.bitcoinj.core.ECKey) TransactionInput(org.bitcoinj.core.TransactionInput) Coin(org.bitcoinj.core.Coin) Script(org.bitcoinj.script.Script) NetworkParameters(org.bitcoinj.core.NetworkParameters) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionOutPoint(org.bitcoinj.core.TransactionOutPoint) Triple(org.apache.commons.lang3.tuple.Triple) UTXO(com.samourai.wallet.send.UTXO) Transaction(org.bitcoinj.core.Transaction)

Aggregations

UTXO (com.samourai.wallet.send.UTXO)35 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)33 ArrayList (java.util.ArrayList)23 HashMap (java.util.HashMap)19 Transaction (org.bitcoinj.core.Transaction)17 IOException (java.io.IOException)14 TransactionOutPoint (org.bitcoinj.core.TransactionOutPoint)14 BlockedUTXO (com.samourai.wallet.send.BlockedUTXO)13 BigInteger (java.math.BigInteger)13 MnemonicException (org.bitcoinj.crypto.MnemonicException)13 JSONException (org.json.JSONException)13 JSONObject (org.json.JSONObject)13 ECKey (org.bitcoinj.core.ECKey)12 TransactionInput (org.bitcoinj.core.TransactionInput)10 Intent (android.content.Intent)9 SegwitAddress (com.samourai.wallet.segwit.SegwitAddress)9 Script (org.bitcoinj.script.Script)9 AlertDialog (android.app.AlertDialog)8 DecryptionException (com.samourai.wallet.crypto.DecryptionException)8 DialogInterface (android.content.DialogInterface)6