Search in sources :

Example 41 with Transaction

use of org.bitcoinj.core.Transaction in project samourai-wallet-android by Samourai-Wallet.

the class SendFactory method signTransactionForSweep.

public Transaction signTransactionForSweep(Transaction unsignedTx, PrivKeyReader privKeyReader) {
    HashMap<String, ECKey> keyBag = new HashMap<String, ECKey>();
    for (TransactionInput input : unsignedTx.getInputs()) {
        try {
            byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript();
            String script = Hex.toHexString(scriptBytes);
            String address = null;
            if (Bech32Util.getInstance().isBech32Script(script)) {
                try {
                    address = Bech32Util.getInstance().getAddressFromScript(script);
                } catch (Exception e) {
                    ;
                }
            } else {
                address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
            }
            Log.i("address from script", address);
            ECKey ecKey = null;
            try {
                DumpedPrivateKey pk = new DumpedPrivateKey(SamouraiWallet.getInstance().getCurrentNetworkParams(), privKeyReader.getKey().getPrivateKeyAsWiF(SamouraiWallet.getInstance().getCurrentNetworkParams()));
                ecKey = pk.getKey();
            // Log.i("SendFactory", "ECKey address:" + ecKey.toAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString());
            } catch (AddressFormatException afe) {
                afe.printStackTrace();
                continue;
            }
            if (ecKey != null) {
                keyBag.put(input.getOutpoint().toString(), ecKey);
            } else {
                Toast.makeText(context, R.string.cannot_recognize_privkey, Toast.LENGTH_SHORT).show();
            // Log.i("ECKey error", "cannot process private key");
            }
        } catch (ScriptException se) {
            ;
        } catch (Exception e) {
            ;
        }
    }
    Transaction signedTx = signTransaction(unsignedTx, keyBag);
    if (signedTx == null) {
        return null;
    } else {
        String hexString = new String(Hex.encode(signedTx.bitcoinSerialize()));
        if (hexString.length() > (100 * 1024)) {
            Toast.makeText(context, R.string.tx_length_error, Toast.LENGTH_SHORT).show();
        // Log.i("SendFactory", "Transaction length too long");
        }
        return signedTx;
    }
}
Also used : Script(org.bitcoinj.script.Script) AddressFormatException(org.bitcoinj.core.AddressFormatException) ScriptException(org.bitcoinj.script.ScriptException) Transaction(org.bitcoinj.core.Transaction) HashMap(java.util.HashMap) ECKey(org.bitcoinj.core.ECKey) DumpedPrivateKey(org.bitcoinj.core.DumpedPrivateKey) TransactionInput(org.bitcoinj.core.TransactionInput) ScriptException(org.bitcoinj.script.ScriptException) AddressFormatException(org.bitcoinj.core.AddressFormatException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException)

Example 42 with Transaction

use of org.bitcoinj.core.Transaction in project samourai-wallet-android by Samourai-Wallet.

the class SendFactory method signTransaction.

public Transaction signTransaction(Transaction unsignedTx, int account) {
    HashMap<String, ECKey> keyBag = new HashMap<String, ECKey>();
    for (TransactionInput input : unsignedTx.getInputs()) {
        try {
            byte[] scriptBytes = input.getOutpoint().getConnectedPubKeyScript();
            String address = null;
            // Log.i("SendFactory", "connected pubkey script:" + Hex.toHexString(scriptBytes));
            if (Bech32Util.getInstance().isBech32Script(Hex.toHexString(scriptBytes))) {
                address = Bech32Util.getInstance().getAddressFromScript(Hex.toHexString(scriptBytes));
            } else {
                address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
            }
            // Log.i("SendFactory", "address from script:" + address);
            ECKey ecKey = null;
            ecKey = getPrivKey(address, account);
            if (ecKey != null) {
                keyBag.put(input.getOutpoint().toString(), ecKey);
            } else {
                throw new RuntimeException("ECKey error: cannot process private key");
            // Log.i("ECKey error", "cannot process private key");
            }
        } catch (ScriptException se) {
            ;
        } catch (Exception e) {
            ;
        }
    }
    Transaction signedTx = signTransaction(unsignedTx, keyBag);
    if (signedTx == null) {
        return null;
    } else {
        String hexString = new String(Hex.encode(signedTx.bitcoinSerialize()));
        if (hexString.length() > (100 * 1024)) {
            Toast.makeText(context, R.string.tx_length_error, Toast.LENGTH_SHORT).show();
        // Log.i("SendFactory", "Transaction length too long");
        }
        return signedTx;
    }
}
Also used : Script(org.bitcoinj.script.Script) ScriptException(org.bitcoinj.script.ScriptException) Transaction(org.bitcoinj.core.Transaction) HashMap(java.util.HashMap) ECKey(org.bitcoinj.core.ECKey) TransactionInput(org.bitcoinj.core.TransactionInput) ScriptException(org.bitcoinj.script.ScriptException) AddressFormatException(org.bitcoinj.core.AddressFormatException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException)

Example 43 with Transaction

use of org.bitcoinj.core.Transaction in project samourai-wallet-android by Samourai-Wallet.

the class BatchSendActivity method doSpend.

private void doSpend() {
    final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
    long amount = 0L;
    for (BatchSendUtil.BatchSend _data : data) {
        Log.d("BatchSendActivity", "output:" + _data.amount);
        Log.d("BatchSendActivity", "output:" + _data.addr);
        Log.d("BatchSendActivity", "output:" + _data.pcode);
        amount += _data.amount;
        if (receivers.containsKey(_data.addr)) {
            BigInteger _amount = receivers.get(_data.addr);
            receivers.put(_data.addr, _amount.add(BigInteger.valueOf(_data.amount)));
        } else {
            receivers.put(_data.addr, BigInteger.valueOf(_data.amount));
        }
    }
    Log.d("BatchSendActivity", "amount:" + amount);
    List<UTXO> utxos = APIFactory.getInstance(BatchSendActivity.this).getUtxos(true);
    Collections.sort(utxos, new UTXO.UTXOComparator());
    List<UTXO> selectedUTXO = new ArrayList<UTXO>();
    int p2pkh = 0;
    int p2sh_p2wpkh = 0;
    int p2wpkh = 0;
    long totalValueSelected = 0L;
    int totalSelected = 0;
    for (UTXO utxo : utxos) {
        Log.d("BatchSendActivity", "utxo value:" + utxo.getValue());
        selectedUTXO.add(utxo);
        totalValueSelected += utxo.getValue();
        totalSelected += utxo.getOutpoints().size();
        Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(utxo.getOutpoints()));
        p2pkh += outpointTypes.getLeft();
        p2sh_p2wpkh += outpointTypes.getMiddle();
        p2wpkh += outpointTypes.getRight();
        if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFeeSegwit(p2pkh, p2sh_p2wpkh, p2wpkh, receivers.size() + 1).longValue())) {
            break;
        }
    }
    Log.d("BatchSendActivity", "totalSelected:" + totalSelected);
    Log.d("BatchSendActivity", "totalValueSelected:" + totalValueSelected);
    final List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
    for (UTXO utxo : selectedUTXO) {
        outpoints.addAll(utxo.getOutpoints());
        for (MyTransactionOutPoint out : utxo.getOutpoints()) {
            Log.d("BatchSendActivity", "outpoint hash:" + out.getTxHash().toString());
            Log.d("BatchSendActivity", "outpoint idx:" + out.getTxOutputN());
            Log.d("BatchSendActivity", "outpoint address:" + out.getAddress());
        }
    }
    Triple<Integer, Integer, Integer> outpointTypes = FeeUtil.getInstance().getOutpointCount(new Vector(outpoints));
    BigInteger fee = FeeUtil.getInstance().estimatedFeeSegwit(outpointTypes.getLeft(), outpointTypes.getMiddle(), outpointTypes.getRight(), receivers.size() + 1);
    Log.d("BatchSendActivity", "fee:" + fee.longValue());
    if (amount + fee.longValue() > balance) {
        Toast.makeText(BatchSendActivity.this, R.string.insufficient_funds, Toast.LENGTH_SHORT).show();
        return;
    }
    long changeAmount = totalValueSelected - (amount + fee.longValue());
    String change_address = null;
    int change_idx = 0;
    if (changeAmount > 0L) {
        change_idx = BIP84Util.getInstance(BatchSendActivity.this).getWallet().getAccount(0).getChange().getAddrIdx();
        change_address = BIP84Util.getInstance(BatchSendActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, change_idx).getBech32AsString();
        receivers.put(change_address, BigInteger.valueOf(changeAmount));
        Log.d("BatchSendActivity", "change output:" + changeAmount);
        Log.d("BatchSendActivity", "change output:" + change_address);
    }
    Transaction tx = SendFactory.getInstance(BatchSendActivity.this).makeTransaction(0, outpoints, receivers);
    if (tx != null) {
        final RBFSpend rbf;
        if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
            rbf = new RBFSpend();
            for (TransactionInput input : tx.getInputs()) {
                boolean _isBIP49 = false;
                boolean _isBIP84 = false;
                String _addr = null;
                String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
                if (Bech32Util.getInstance().isBech32Script(script)) {
                    try {
                        _addr = Bech32Util.getInstance().getAddressFromScript(script);
                        _isBIP84 = true;
                    } catch (Exception e) {
                        ;
                    }
                } else {
                    Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    if (_address != null) {
                        _addr = _address.toString();
                        _isBIP49 = true;
                    }
                }
                if (_addr == null) {
                    _addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                }
                String path = APIFactory.getInstance(BatchSendActivity.this).getUnspentPaths().get(_addr);
                if (path != null) {
                    if (_isBIP84) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/84");
                    } else if (_isBIP49) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/49");
                    } else {
                        rbf.addKey(input.getOutpoint().toString(), path);
                    }
                } else {
                    String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
                    int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
                    rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
                }
            }
        } else {
            rbf = null;
        }
        String strChangeIsDust = "";
        String strPrivacyWarning = "";
        String strMessage = strChangeIsDust + strPrivacyWarning + "Send " + Coin.valueOf(amount).toPlainString() + " BTC. (fee:" + Coin.valueOf(fee.longValue()).toPlainString() + ")?\n";
        final long _change = changeAmount;
        final String _change_address = change_address;
        final int _change_idx = change_idx;
        final long _amount = amount;
        tx = SendFactory.getInstance(BatchSendActivity.this).signTransaction(tx, 0);
        final String hexTx = new String(Hex.encode(tx.bitcoinSerialize()));
        final String strTxHash = tx.getHashAsString();
        // Log.d("BatchSendActivity", "tx hash:" + tx.getHashAsString());
        // Log.d("BatchSendActivity", "hex signed tx:" + hexTx);
        AlertDialog.Builder dlg = new AlertDialog.Builder(BatchSendActivity.this).setTitle(R.string.app_name).setMessage(strMessage).setCancelable(false).setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

            public void onClick(final DialogInterface dialog, int whichButton) {
                dialog.dismiss();
                if (PrefsUtil.getInstance(BatchSendActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
                    doShowTx(hexTx, strTxHash);
                    return;
                }
                SendParams.getInstance().setParams(outpoints, receivers, data, SendActivity.SPEND_SIMPLE, _change, 84, 0, "", false, false, _amount, _change_idx);
                Intent _intent = new Intent(BatchSendActivity.this, TxAnimUIActivity.class);
                startActivity(_intent);
            }
        }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();
            }
        });
        if (!isFinishing()) {
            dlg.show();
        }
    } else {
        // Log.d("SendActivity", "tx error");
        Toast.makeText(BatchSendActivity.this, "tx error", Toast.LENGTH_SHORT).show();
    }
}
Also used : AlertDialog(android.app.AlertDialog) Address(org.bitcoinj.core.Address) PaymentAddress(com.samourai.wallet.bip47.rpc.PaymentAddress) SegwitAddress(com.samourai.wallet.segwit.SegwitAddress) HashMap(java.util.HashMap) BatchSendUtil(com.samourai.wallet.util.BatchSendUtil) DialogInterface(android.content.DialogInterface) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) TransactionInput(org.bitcoinj.core.TransactionInput) RBFSpend(com.samourai.wallet.send.RBFSpend) Vector(java.util.Vector) Intent(android.content.Intent) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) Point(android.graphics.Point) WriterException(com.google.zxing.WriterException) ParseException(java.text.ParseException) FileNotFoundException(java.io.FileNotFoundException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) BigInteger(java.math.BigInteger) UTXO(com.samourai.wallet.send.UTXO) Transaction(org.bitcoinj.core.Transaction) BigInteger(java.math.BigInteger)

Example 44 with Transaction

use of org.bitcoinj.core.Transaction in project samourai-wallet-android by Samourai-Wallet.

the class TxAnimUIActivity method onCreate.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_tx_anim_ui);
    progressView = findViewById(R.id.transactionProgressView);
    progressView.reset();
    progressView.setTxStatusMessage(R.string.tx_creating_ok);
    progressView.getmArcProgress().startArc1(arcdelay);
    // make tx
    final Transaction tx = SendFactory.getInstance(TxAnimUIActivity.this).makeTransaction(SendParams.getInstance().getAccount(), SendParams.getInstance().getOutpoints(), SendParams.getInstance().getReceivers());
    if (tx == null) {
        failTx(R.string.tx_creating_ko);
    } else {
        // Toast.makeText(TxAnimUIActivity.this, "tx created OK", Toast.LENGTH_SHORT).show();
        final RBFSpend rbf;
        if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.RBF_OPT_IN, false) == true) {
            rbf = new RBFSpend();
            for (TransactionInput input : tx.getInputs()) {
                boolean _isBIP49 = false;
                boolean _isBIP84 = false;
                String _addr = null;
                String script = Hex.toHexString(input.getConnectedOutput().getScriptBytes());
                if (Bech32Util.getInstance().isBech32Script(script)) {
                    try {
                        _addr = Bech32Util.getInstance().getAddressFromScript(script);
                        _isBIP84 = true;
                    } catch (Exception e) {
                        ;
                    }
                } else {
                    Address _address = input.getConnectedOutput().getAddressFromP2SH(SamouraiWallet.getInstance().getCurrentNetworkParams());
                    if (_address != null) {
                        _addr = _address.toString();
                        _isBIP49 = true;
                    }
                }
                if (_addr == null) {
                    _addr = input.getConnectedOutput().getAddressFromP2PKHScript(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
                }
                String path = APIFactory.getInstance(TxAnimUIActivity.this).getUnspentPaths().get(_addr);
                if (path != null) {
                    if (_isBIP84) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/84");
                    } else if (_isBIP49) {
                        rbf.addKey(input.getOutpoint().toString(), path + "/49");
                    } else {
                        rbf.addKey(input.getOutpoint().toString(), path);
                    }
                } else {
                    String pcode = BIP47Meta.getInstance().getPCode4Addr(_addr);
                    int idx = BIP47Meta.getInstance().getIdx4Addr(_addr);
                    rbf.addKey(input.getOutpoint().toString(), pcode + "/" + idx);
                }
            }
        } else {
            rbf = null;
        }
        new Handler().postDelayed(new Runnable() {

            @Override
            public void run() {
                TxAnimUIActivity.this.runOnUiThread(() -> {
                    progressView.getmArcProgress().startArc2(arcdelay);
                    progressView.setTxStatusMessage(R.string.tx_signing_ok);
                });
                final Transaction _tx = SendFactory.getInstance(TxAnimUIActivity.this).signTransaction(tx, SendParams.getInstance().getAccount());
                if (_tx == null) {
                    failTx(R.string.tx_signing_ko);
                } else {
                // Toast.makeText(TxAnimUIActivity.this, "tx signed OK", Toast.LENGTH_SHORT).show();
                }
                final String hexTx = new String(Hex.encode(_tx.bitcoinSerialize()));
                Log.d("TxAnimUIActivity", "hex tx:" + hexTx);
                final String strTxHash = _tx.getHashAsString();
                resultHandler = new Handler();
                new Handler().postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        TxAnimUIActivity.this.runOnUiThread(() -> {
                            progressView.getmArcProgress().startArc3(arcdelay);
                            progressView.setTxStatusMessage(R.string.tx_broadcast_ok);
                        });
                        if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.BROADCAST_TX, true) == false) {
                            offlineTx(R.string.broadcast_off, hexTx, strTxHash);
                            return;
                        }
                        if (AppUtil.getInstance(TxAnimUIActivity.this).isOfflineMode()) {
                            offlineTx(R.string.offline_mode, hexTx, strTxHash);
                            return;
                        }
                        new Thread(new Runnable() {

                            @Override
                            public void run() {
                                Looper.prepare();
                                boolean isOK = false;
                                String response = PushTx.getInstance(TxAnimUIActivity.this).samourai(hexTx);
                                try {
                                    if (PrefsUtil.getInstance(TxAnimUIActivity.this).getValue(PrefsUtil.USE_TRUSTED_NODE, false) == true) {
                                        if (TrustedNodeUtil.getInstance().isSet()) {
                                            response = PushTx.getInstance(TxAnimUIActivity.this).trustedNode(hexTx);
                                            JSONObject jsonObject = new org.json.JSONObject(response);
                                            if (jsonObject.has("result")) {
                                                if (jsonObject.getString("result").matches("^[A-Za-z0-9]{64}$")) {
                                                    isOK = true;
                                                } else {
                                                    Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_tx_error, Toast.LENGTH_SHORT).show();
                                                    failTx(R.string.tx_broadcast_ko);
                                                }
                                            }
                                        } else {
                                            Toast.makeText(TxAnimUIActivity.this, R.string.trusted_node_not_valid, Toast.LENGTH_SHORT).show();
                                            failTx(R.string.tx_broadcast_ko);
                                        }
                                    } else {
                                        if (response != null) {
                                            JSONObject jsonObject = new org.json.JSONObject(response);
                                            if (jsonObject.has("status")) {
                                                if (jsonObject.getString("status").equals("ok")) {
                                                    isOK = true;
                                                }
                                            }
                                        } else {
                                            Toast.makeText(TxAnimUIActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
                                            failTx(R.string.tx_broadcast_ko);
                                        }
                                    }
                                } catch (JSONException je) {
                                    failTx(R.string.tx_broadcast_ko);
                                }
                                final boolean _isOK = isOK;
                                resultHandler.postDelayed(new Runnable() {

                                    @Override
                                    public void run() {
                                        if (_isOK) {
                                            progressView.showCheck();
                                            progressView.setTxStatusMessage(R.string.tx_sent_ok);
                                        } else {
                                            failTx(R.string.tx_sent_ko);
                                        }
                                        handleResult(_isOK, rbf, strTxHash, hexTx, _tx);
                                    }
                                }, resultDelay);
                                Looper.loop();
                            }
                        }).start();
                    }
                }, broadcastDelay);
            }
        }, signDelay);
    }
}
Also used : Address(org.bitcoinj.core.Address) Handler(android.os.Handler) JSONException(org.json.JSONException) TransactionInput(org.bitcoinj.core.TransactionInput) WriterException(com.google.zxing.WriterException) JSONException(org.json.JSONException) DecoderException(org.spongycastle.util.encoders.DecoderException) FileNotFoundException(java.io.FileNotFoundException) MnemonicException(org.bitcoinj.crypto.MnemonicException) IOException(java.io.IOException) Point(android.graphics.Point) Transaction(org.bitcoinj.core.Transaction) JSONObject(org.json.JSONObject) RBFSpend(com.samourai.wallet.send.RBFSpend)

Example 45 with Transaction

use of org.bitcoinj.core.Transaction in project samourai-wallet-android by Samourai-Wallet.

the class PayNymDetailsActivity method doNotifTx.

private void doNotifTx() {
    // 
    // get wallet balance
    // 
    long balance = 0L;
    try {
        balance = APIFactory.getInstance(PayNymDetailsActivity.this).getXpubAmounts().get(HD_WalletFactory.getInstance(PayNymDetailsActivity.this).get().getAccount(0).xpubstr());
    } catch (IOException ioe) {
        balance = 0L;
    } catch (MnemonicException.MnemonicLengthException mle) {
        balance = 0L;
    } catch (java.lang.NullPointerException npe) {
        balance = 0L;
    }
    final List<UTXO> selectedUTXO = new ArrayList<UTXO>();
    long totalValueSelected = 0L;
    // long change = 0L;
    BigInteger fee = null;
    // 
    // spend dust threshold amount to notification address
    // 
    long amount = SendNotifTxFactory._bNotifTxValue.longValue();
    // 
    // add Samourai Wallet fee to total amount
    // 
    amount += SendNotifTxFactory._bSWFee.longValue();
    // 
    // get unspents
    // 
    List<UTXO> utxos = null;
    if (UTXOFactory.getInstance().getTotalP2SH_P2WPKH() > amount + FeeUtil.getInstance().estimatedFeeSegwit(0, 1, 4).longValue()) {
        utxos = new ArrayList<UTXO>();
        utxos.addAll(UTXOFactory.getInstance().getAllP2SH_P2WPKH().values());
    } else {
        utxos = APIFactory.getInstance(PayNymDetailsActivity.this).getUtxos(true);
    }
    // sort in ascending order by value
    final List<UTXO> _utxos = utxos;
    Collections.sort(_utxos, new UTXO.UTXOComparator());
    Collections.reverse(_utxos);
    // 
    for (UTXO u : _utxos) {
        if (u.getValue() >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFee(1, 4).longValue())) {
            selectedUTXO.add(u);
            totalValueSelected += u.getValue();
            Log.d("PayNymDetailsActivity", "single output");
            Log.d("PayNymDetailsActivity", "value selected:" + u.getValue());
            Log.d("PayNymDetailsActivity", "total value selected:" + totalValueSelected);
            Log.d("PayNymDetailsActivity", "nb inputs:" + u.getOutpoints().size());
            break;
        }
    }
    // 
    // use normal fee settings
    // 
    SuggestedFee suggestedFee = FeeUtil.getInstance().getSuggestedFee();
    long lo = FeeUtil.getInstance().getLowFee().getDefaultPerKB().longValue() / 1000L;
    long mi = FeeUtil.getInstance().getNormalFee().getDefaultPerKB().longValue() / 1000L;
    long hi = FeeUtil.getInstance().getHighFee().getDefaultPerKB().longValue() / 1000L;
    if (lo == mi && mi == hi) {
        SuggestedFee hi_sf = new SuggestedFee();
        hi_sf.setDefaultPerKB(BigInteger.valueOf((long) (hi * 1.15 * 1000.0)));
        FeeUtil.getInstance().setSuggestedFee(hi_sf);
    } else if (lo == mi) {
        FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getHighFee());
    } else {
        FeeUtil.getInstance().setSuggestedFee(FeeUtil.getInstance().getNormalFee());
    }
    if (selectedUTXO.size() == 0) {
        // sort in descending order by value
        Collections.sort(_utxos, new UTXO.UTXOComparator());
        int selected = 0;
        // get largest UTXOs > than spend + fee + dust
        for (UTXO u : _utxos) {
            selectedUTXO.add(u);
            totalValueSelected += u.getValue();
            selected += u.getOutpoints().size();
            if (totalValueSelected >= (amount + SamouraiWallet.bDust.longValue() + FeeUtil.getInstance().estimatedFee(selected, 4).longValue())) {
                Log.d("PayNymDetailsActivity", "multiple outputs");
                Log.d("PayNymDetailsActivity", "total value selected:" + totalValueSelected);
                Log.d("PayNymDetailsActivity", "nb inputs:" + u.getOutpoints().size());
                break;
            }
        }
        // fee = FeeUtil.getInstance().estimatedFee(selected, 4);
        fee = FeeUtil.getInstance().estimatedFee(selected, 7);
    } else {
        // fee = FeeUtil.getInstance().estimatedFee(1, 4);
        fee = FeeUtil.getInstance().estimatedFee(1, 7);
    }
    // 
    // reset fee to previous setting
    // 
    FeeUtil.getInstance().setSuggestedFee(suggestedFee);
    // 
    if ((amount + fee.longValue()) >= balance) {
        String message = getText(R.string.bip47_notif_tx_insufficient_funds_1) + " ";
        BigInteger biAmount = SendNotifTxFactory._bSWFee.add(SendNotifTxFactory._bNotifTxValue.add(FeeUtil.getInstance().estimatedFee(1, 4, FeeUtil.getInstance().getLowFee().getDefaultPerKB())));
        String strAmount = MonetaryUtil.getInstance().getBTCFormat().format(((double) biAmount.longValue()) / 1e8) + " BTC ";
        message += strAmount;
        message += " " + getText(R.string.bip47_notif_tx_insufficient_funds_2);
        AlertDialog.Builder dlg = new AlertDialog.Builder(PayNymDetailsActivity.this).setTitle(R.string.app_name).setMessage(message).setCancelable(false).setPositiveButton(R.string.help, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://support.samourai.io/article/58-connecting-to-a-paynym-contact"));
                startActivity(browserIntent);
            }
        }).setNegativeButton(R.string.close, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int whichButton) {
                dialog.dismiss();
            }
        });
        if (!isFinishing()) {
            dlg.show();
        }
        return;
    }
    // 
    // payment code to be notified
    // 
    PaymentCode payment_code;
    try {
        payment_code = new PaymentCode(pcode);
    } catch (AddressFormatException afe) {
        payment_code = null;
    }
    if (payment_code == null) {
        return;
    }
    // 
    // create outpoints for spend later
    // 
    final List<MyTransactionOutPoint> outpoints = new ArrayList<MyTransactionOutPoint>();
    for (UTXO u : selectedUTXO) {
        outpoints.addAll(u.getOutpoints());
    }
    // 
    // create inputs from outpoints
    // 
    List<MyTransactionInput> inputs = new ArrayList<MyTransactionInput>();
    for (MyTransactionOutPoint o : outpoints) {
        Script script = new Script(o.getScriptBytes());
        if (script.getScriptType() == Script.ScriptType.NO_TYPE) {
            continue;
        }
        MyTransactionInput input = new MyTransactionInput(SamouraiWallet.getInstance().getCurrentNetworkParams(), null, new byte[0], o, o.getTxHash().toString(), o.getTxOutputN());
        inputs.add(input);
    }
    // 
    // sort inputs
    // 
    Collections.sort(inputs, new SendFactory.BIP69InputComparator());
    // 
    // find outpoint that corresponds to 0th input
    // 
    MyTransactionOutPoint outPoint = null;
    for (MyTransactionOutPoint o : outpoints) {
        if (o.getTxHash().toString().equals(inputs.get(0).getTxHash()) && o.getTxOutputN() == inputs.get(0).getTxPos()) {
            outPoint = o;
            break;
        }
    }
    if (outPoint == null) {
        Toast.makeText(PayNymDetailsActivity.this, R.string.bip47_cannot_identify_outpoint, Toast.LENGTH_SHORT).show();
        return;
    }
    byte[] op_return = null;
    // 
    try {
        // Script inputScript = new Script(outPoint.getConnectedPubKeyScript());
        byte[] scriptBytes = outPoint.getConnectedPubKeyScript();
        String address = null;
        if (Bech32Util.getInstance().isBech32Script(Hex.toHexString(scriptBytes))) {
            address = Bech32Util.getInstance().getAddressFromScript(Hex.toHexString(scriptBytes));
        } else {
            address = new Script(scriptBytes).getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
        }
        // String address = inputScript.getToAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).toString();
        ECKey ecKey = SendFactory.getPrivKey(address, 0);
        if (ecKey == null || !ecKey.hasPrivKey()) {
            Toast.makeText(PayNymDetailsActivity.this, R.string.bip47_cannot_compose_notif_tx, Toast.LENGTH_SHORT).show();
            return;
        }
        // 
        // use outpoint for payload masking
        // 
        byte[] privkey = ecKey.getPrivKeyBytes();
        byte[] pubkey = payment_code.notificationAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).getPubKey();
        byte[] outpoint = outPoint.bitcoinSerialize();
        // Log.i("PayNymDetailsActivity", "outpoint:" + Hex.toHexString(outpoint));
        // Log.i("PayNymDetailsActivity", "payer shared secret:" + Hex.toHexString(new SecretPoint(privkey, pubkey).ECDHSecretAsBytes()));
        byte[] mask = PaymentCode.getMask(new SecretPoint(privkey, pubkey).ECDHSecretAsBytes(), outpoint);
        // Log.i("PayNymDetailsActivity", "mask:" + Hex.toHexString(mask));
        // Log.i("PayNymDetailsActivity", "mask length:" + mask.length);
        // Log.i("PayNymDetailsActivity", "payload0:" + Hex.toHexString(BIP47Util.getInstance(context).getPaymentCode().getPayload()));
        op_return = PaymentCode.blind(BIP47Util.getInstance(PayNymDetailsActivity.this).getPaymentCode().getPayload(), mask);
    // Log.i("PayNymDetailsActivity", "payload1:" + Hex.toHexString(op_return));
    } catch (InvalidKeyException ike) {
        Toast.makeText(PayNymDetailsActivity.this, ike.getMessage(), Toast.LENGTH_SHORT).show();
        return;
    } catch (InvalidKeySpecException ikse) {
        Toast.makeText(PayNymDetailsActivity.this, ikse.getMessage(), Toast.LENGTH_SHORT).show();
        return;
    } catch (NoSuchAlgorithmException nsae) {
        Toast.makeText(PayNymDetailsActivity.this, nsae.getMessage(), Toast.LENGTH_SHORT).show();
        return;
    } catch (NoSuchProviderException nspe) {
        Toast.makeText(PayNymDetailsActivity.this, nspe.getMessage(), Toast.LENGTH_SHORT).show();
        return;
    } catch (Exception e) {
        Toast.makeText(PayNymDetailsActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
        return;
    }
    final HashMap<String, BigInteger> receivers = new HashMap<String, BigInteger>();
    receivers.put(Hex.toHexString(op_return), BigInteger.ZERO);
    receivers.put(payment_code.notificationAddress(SamouraiWallet.getInstance().getCurrentNetworkParams()).getAddressString(), SendNotifTxFactory._bNotifTxValue);
    receivers.put(SamouraiWallet.getInstance().isTestNet() ? SendNotifTxFactory.TESTNET_SAMOURAI_NOTIF_TX_FEE_ADDRESS : SendNotifTxFactory.SAMOURAI_NOTIF_TX_FEE_ADDRESS, SendNotifTxFactory._bSWFee);
    final long change = totalValueSelected - (amount + fee.longValue());
    if (change > 0L) {
        String change_address = BIP84Util.getInstance(PayNymDetailsActivity.this).getAddressAt(AddressFactory.CHANGE_CHAIN, BIP84Util.getInstance(PayNymDetailsActivity.this).getWallet().getAccount(0).getChange().getAddrIdx()).getBech32AsString();
        receivers.put(change_address, BigInteger.valueOf(change));
    }
    Log.d("PayNymDetailsActivity", "outpoints:" + outpoints.size());
    Log.d("PayNymDetailsActivity", "totalValueSelected:" + BigInteger.valueOf(totalValueSelected).toString());
    Log.d("PayNymDetailsActivity", "amount:" + BigInteger.valueOf(amount).toString());
    Log.d("PayNymDetailsActivity", "change:" + BigInteger.valueOf(change).toString());
    Log.d("PayNymDetailsActivity", "fee:" + fee.toString());
    if (change < 0L) {
        Toast.makeText(PayNymDetailsActivity.this, R.string.bip47_cannot_compose_notif_tx, Toast.LENGTH_SHORT).show();
        return;
    }
    final MyTransactionOutPoint _outPoint = outPoint;
    String strNotifTxMsg = getText(R.string.bip47_setup4_text1) + " ";
    long notifAmount = amount;
    String strAmount = MonetaryUtil.getInstance().getBTCFormat().format(((double) notifAmount + fee.longValue()) / 1e8) + " BTC ";
    strNotifTxMsg += strAmount + getText(R.string.bip47_setup4_text2);
    showFollowAlert(strAmount, view -> {
        new Thread(new Runnable() {

            @Override
            public void run() {
                Looper.prepare();
                Transaction tx = SendFactory.getInstance(PayNymDetailsActivity.this).makeTransaction(0, outpoints, receivers);
                if (tx != null) {
                    String input0hash = tx.getInput(0L).getOutpoint().getHash().toString();
                    Log.d("PayNymDetailsActivity", "input0 hash:" + input0hash);
                    Log.d("PayNymDetailsActivity", "_outPoint hash:" + _outPoint.getTxHash().toString());
                    int input0index = (int) tx.getInput(0L).getOutpoint().getIndex();
                    Log.d("PayNymDetailsActivity", "input0 index:" + input0index);
                    Log.d("PayNymDetailsActivity", "_outPoint index:" + _outPoint.getTxOutputN());
                    if (!input0hash.equals(_outPoint.getTxHash().toString()) || input0index != _outPoint.getTxOutputN()) {
                        Toast.makeText(PayNymDetailsActivity.this, R.string.bip47_cannot_compose_notif_tx, Toast.LENGTH_SHORT).show();
                        return;
                    }
                    tx = SendFactory.getInstance(PayNymDetailsActivity.this).signTransaction(tx, 0);
                    final String hexTx = new String(org.bouncycastle.util.encoders.Hex.encode(tx.bitcoinSerialize()));
                    Log.d("SendActivity", tx.getHashAsString());
                    Log.d("SendActivity", hexTx);
                    boolean isOK = false;
                    String response = null;
                    try {
                        response = PushTx.getInstance(PayNymDetailsActivity.this).samourai(hexTx);
                        Log.d("SendActivity", "pushTx:" + response);
                        if (response != null) {
                            org.json.JSONObject jsonObject = new org.json.JSONObject(response);
                            if (jsonObject.has("status")) {
                                if (jsonObject.getString("status").equals("ok")) {
                                    isOK = true;
                                }
                            }
                        } else {
                            Toast.makeText(PayNymDetailsActivity.this, R.string.pushtx_returns_null, Toast.LENGTH_SHORT).show();
                            return;
                        }
                        if (isOK) {
                            Toast.makeText(PayNymDetailsActivity.this, R.string.payment_channel_init, Toast.LENGTH_SHORT).show();
                            // 
                            // set outgoing index for payment code to 0
                            // 
                            BIP47Meta.getInstance().setOutgoingIdx(pcode, 0);
                            // Log.i("SendNotifTxFactory", "tx hash:" + tx.getHashAsString());
                            // 
                            // status to NO_CFM
                            // 
                            BIP47Meta.getInstance().setOutgoingStatus(pcode, tx.getHashAsString(), BIP47Meta.STATUS_SENT_NO_CFM);
                            // 
                            if (change > 0L) {
                                BIP49Util.getInstance(PayNymDetailsActivity.this).getWallet().getAccount(0).getChange().incAddrIdx();
                            }
                            savePayLoad();
                        } else {
                            Toast.makeText(PayNymDetailsActivity.this, R.string.tx_failed, Toast.LENGTH_SHORT).show();
                        }
                        runOnUiThread(() -> {
                            setPayNym();
                        });
                    } catch (JSONException je) {
                        Toast.makeText(PayNymDetailsActivity.this, "pushTx:" + je.getMessage(), Toast.LENGTH_SHORT).show();
                        return;
                    } catch (MnemonicException.MnemonicLengthException mle) {
                        Toast.makeText(PayNymDetailsActivity.this, "pushTx:" + mle.getMessage(), Toast.LENGTH_SHORT).show();
                        return;
                    } catch (DecoderException de) {
                        Toast.makeText(PayNymDetailsActivity.this, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
                        return;
                    } catch (IOException ioe) {
                        Toast.makeText(PayNymDetailsActivity.this, "pushTx:" + ioe.getMessage(), Toast.LENGTH_SHORT).show();
                        return;
                    } catch (DecryptionException de) {
                        Toast.makeText(PayNymDetailsActivity.this, "pushTx:" + de.getMessage(), Toast.LENGTH_SHORT).show();
                        return;
                    }
                }
                Looper.loop();
            }
        }).start();
    });
}
Also used : AlertDialog(android.app.AlertDialog) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) ECKey(org.bitcoinj.core.ECKey) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) JSONObject(org.json.JSONObject) InvalidKeySpecException(java.security.spec.InvalidKeySpecException) SecretPoint(com.samourai.wallet.bip47.rpc.SecretPoint) AddressFormatException(org.bitcoinj.core.AddressFormatException) Script(org.bitcoinj.script.Script) SuggestedFee(com.samourai.wallet.send.SuggestedFee) Transaction(org.bitcoinj.core.Transaction) JSONObject(org.json.JSONObject) BigInteger(java.math.BigInteger) DialogInterface(android.content.DialogInterface) MnemonicException(org.bitcoinj.crypto.MnemonicException) PaymentCode(com.samourai.wallet.bip47.rpc.PaymentCode) SendFactory(com.samourai.wallet.send.SendFactory) JSONException(org.json.JSONException) Intent(android.content.Intent) IOException(java.io.IOException) InvalidKeyException(java.security.InvalidKeyException) MyTransactionOutPoint(com.samourai.wallet.send.MyTransactionOutPoint) SecretPoint(com.samourai.wallet.bip47.rpc.SecretPoint) JSONException(org.json.JSONException) DecoderException(org.bouncycastle.util.encoders.DecoderException) 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) DecoderException(org.bouncycastle.util.encoders.DecoderException) MyTransactionInput(com.samourai.wallet.send.MyTransactionInput) NoSuchProviderException(java.security.NoSuchProviderException) DecryptionException(com.samourai.wallet.crypto.DecryptionException)

Aggregations

Transaction (org.bitcoinj.core.Transaction)214 Coin (org.bitcoinj.core.Coin)71 TransactionInput (org.bitcoinj.core.TransactionInput)48 TransactionOutput (org.bitcoinj.core.TransactionOutput)42 TransactionOutPoint (org.bitcoinj.core.TransactionOutPoint)38 Address (org.bitcoinj.core.Address)35 ECKey (org.bitcoinj.core.ECKey)32 Script (org.bitcoinj.script.Script)32 ArrayList (java.util.ArrayList)31 HashMap (java.util.HashMap)29 SendRequest (org.bitcoinj.wallet.SendRequest)25 Wallet (org.bitcoinj.wallet.Wallet)25 IOException (java.io.IOException)24 List (java.util.List)24 InsufficientMoneyException (org.bitcoinj.core.InsufficientMoneyException)20 MyTransactionOutPoint (com.samourai.wallet.send.MyTransactionOutPoint)19 AddressFormatException (org.bitcoinj.core.AddressFormatException)19 Sha256Hash (org.bitcoinj.core.Sha256Hash)19 UTXO (com.samourai.wallet.send.UTXO)17 Nullable (javax.annotation.Nullable)17