Search in sources :

Example 6 with PSBT

use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.

the class Payjoin method requestPayjoinPSBT.

public PSBT requestPayjoinPSBT(boolean allowOutputSubstitution) throws PayjoinReceiverException {
    if (!payjoinURI.isPayjoinOutputSubstitutionAllowed()) {
        allowOutputSubstitution = false;
    }
    URI uri = payjoinURI.getPayjoinUrl();
    if (uri == null) {
        log.error("No payjoin URL provided");
        throw new PayjoinReceiverException("No payjoin URL provided");
    }
    try {
        String base64Psbt = psbt.getPublicCopy().toBase64String();
        String appendQuery = "v=1";
        int changeOutputIndex = getChangeOutputIndex();
        long maxAdditionalFeeContribution = 0;
        if (changeOutputIndex > -1) {
            appendQuery += "&additionalfeeoutputindex=" + changeOutputIndex;
            maxAdditionalFeeContribution = getAdditionalFeeContribution();
            appendQuery += "&maxadditionalfeecontribution=" + maxAdditionalFeeContribution;
        }
        URI finalUri = new URI(uri.getScheme(), uri.getAuthority(), uri.getPath(), uri.getQuery() == null ? appendQuery : uri.getQuery() + "&" + appendQuery, uri.getFragment());
        log.info("Sending PSBT to " + finalUri.toURL());
        Proxy proxy = AppServices.getProxy();
        HttpURLConnection connection = proxy == null ? (HttpURLConnection) finalUri.toURL().openConnection() : (HttpURLConnection) finalUri.toURL().openConnection(proxy);
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "text/plain");
        connection.setDoOutput(true);
        try (OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream())) {
            writer.write(base64Psbt);
            writer.flush();
        }
        StringBuilder response = new StringBuilder();
        try (BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
            String responseLine;
            while ((responseLine = br.readLine()) != null) {
                response.append(responseLine.trim());
            }
        }
        int statusCode = connection.getResponseCode();
        if (statusCode != 200) {
            Gson gson = new Gson();
            PayjoinReceiverError payjoinReceiverError = gson.fromJson(response.toString(), PayjoinReceiverError.class);
            log.warn("Payjoin receiver returned an error of " + payjoinReceiverError.getErrorCode() + " (" + payjoinReceiverError.getMessage() + ")");
            throw new PayjoinReceiverException(payjoinReceiverError.getSafeMessage());
        }
        PSBT proposalPsbt = PSBT.fromString(response.toString().trim());
        checkProposal(psbt, proposalPsbt, changeOutputIndex, maxAdditionalFeeContribution, allowOutputSubstitution);
        return proposalPsbt;
    } catch (URISyntaxException e) {
        log.error("Invalid payjoin receiver URI", e);
        throw new PayjoinReceiverException("Invalid payjoin receiver URI", e);
    } catch (IOException e) {
        log.error("Payjoin receiver error", e);
        throw new PayjoinReceiverException("Payjoin receiver error", e);
    } catch (PSBTParseException e) {
        log.error("Error parsing received PSBT", e);
        throw new PayjoinReceiverException("Payjoin receiver returned invalid PSBT", e);
    }
}
Also used : Gson(com.google.gson.Gson) URISyntaxException(java.net.URISyntaxException) URI(java.net.URI) BitcoinURI(com.sparrowwallet.drongo.uri.BitcoinURI) PSBT(com.sparrowwallet.drongo.psbt.PSBT) Proxy(java.net.Proxy) HttpURLConnection(java.net.HttpURLConnection) PSBTParseException(com.sparrowwallet.drongo.psbt.PSBTParseException)

Example 7 with PSBT

use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.

the class PayNymController method broadcastNotificationTransaction.

private void broadcastNotificationTransaction(Wallet decryptedWallet, WalletTransaction walletTransaction, PaymentCode paymentCode, PayNym payNym) {
    try {
        PaymentCode externalPaymentCode = payNym.paymentCode();
        WalletNode input0Node = walletTransaction.getSelectedUtxos().entrySet().iterator().next().getValue();
        Keystore keystore = input0Node.getWallet().isNested() ? decryptedWallet.getChildWallet(input0Node.getWallet().getName()).getKeystores().get(0) : decryptedWallet.getKeystores().get(0);
        ECKey input0Key = keystore.getKey(input0Node);
        TransactionOutPoint input0Outpoint = walletTransaction.getTransaction().getInputs().iterator().next().getOutpoint();
        SecretPoint secretPoint = new SecretPoint(input0Key.getPrivKeyBytes(), externalPaymentCode.getNotificationKey().getPubKey());
        byte[] blindingMask = PaymentCode.getMask(secretPoint.ECDHSecretAsBytes(), input0Outpoint.bitcoinSerialize());
        byte[] blindedPaymentCode = PaymentCode.blind(paymentCode.getPayload(), blindingMask);
        WalletTransaction finalWalletTx = getWalletTransaction(decryptedWallet, payNym, blindedPaymentCode, walletTransaction.getSelectedUtxos().keySet());
        PSBT psbt = finalWalletTx.createPSBT();
        decryptedWallet.sign(psbt);
        decryptedWallet.finalise(psbt);
        Transaction transaction = psbt.extractTransaction();
        ElectrumServer.BroadcastTransactionService broadcastTransactionService = new ElectrumServer.BroadcastTransactionService(transaction);
        broadcastTransactionService.setOnSucceeded(successEvent -> {
            ElectrumServer.TransactionMempoolService transactionMempoolService = new ElectrumServer.TransactionMempoolService(walletTransaction.getWallet(), transaction.getTxId(), new HashSet<>(walletTransaction.getSelectedUtxos().values()));
            transactionMempoolService.setDelay(Duration.seconds(2));
            transactionMempoolService.setPeriod(Duration.seconds(5));
            transactionMempoolService.setRestartOnFailure(false);
            transactionMempoolService.setOnSucceeded(mempoolWorkerStateEvent -> {
                Set<String> scriptHashes = transactionMempoolService.getValue();
                if (!scriptHashes.isEmpty()) {
                    transactionMempoolService.cancel();
                    List<Wallet> addedWallets = addChildWallets(payNym, externalPaymentCode);
                    Wallet masterWallet = getMasterWallet();
                    Storage storage = AppServices.get().getOpenWallets().get(masterWallet);
                    EventManager.get().post(new ChildWalletsAddedEvent(storage, masterWallet, addedWallets));
                    retrievePayNymProgress.setVisible(false);
                    followingList.refresh();
                    BlockTransaction blockTransaction = walletTransaction.getWallet().getWalletTransaction(transaction.getTxId());
                    if (blockTransaction != null && blockTransaction.getLabel() == null) {
                        blockTransaction.setLabel("Link " + payNym.nymName());
                        TransactionEntry transactionEntry = new TransactionEntry(walletTransaction.getWallet(), blockTransaction, Collections.emptyMap(), Collections.emptyMap());
                        EventManager.get().post(new WalletEntryLabelsChangedEvent(walletTransaction.getWallet(), List.of(transactionEntry)));
                    }
                }
                if (transactionMempoolService.getIterationCount() > 5 && transactionMempoolService.isRunning()) {
                    transactionMempoolService.cancel();
                    retrievePayNymProgress.setVisible(false);
                    followingList.refresh();
                    log.error("Timeout searching for broadcasted notification transaction");
                    AppServices.showErrorDialog("Timeout searching for broadcasted transaction", "The transaction was broadcast but the server did not register it in the mempool. It is safe to try linking again.");
                }
            });
            transactionMempoolService.setOnFailed(mempoolWorkerStateEvent -> {
                transactionMempoolService.cancel();
                log.error("Error searching for broadcasted notification transaction", mempoolWorkerStateEvent.getSource().getException());
                retrievePayNymProgress.setVisible(false);
                followingList.refresh();
                AppServices.showErrorDialog("Timeout searching for broadcasted transaction", "The transaction was broadcast but the server did not register it in the mempool. It is safe to try linking again.");
            });
            transactionMempoolService.start();
        });
        broadcastTransactionService.setOnFailed(failedEvent -> {
            log.error("Error broadcasting notification transaction", failedEvent.getSource().getException());
            retrievePayNymProgress.setVisible(false);
            followingList.refresh();
            AppServices.showErrorDialog("Error broadcasting notification transaction", failedEvent.getSource().getException().getMessage());
        });
        retrievePayNymProgress.setVisible(true);
        notificationTransactions.put(transaction.getTxId(), payNym);
        broadcastTransactionService.start();
    } catch (Exception e) {
        log.error("Error creating notification transaction", e);
        retrievePayNymProgress.setVisible(false);
        followingList.refresh();
        AppServices.showErrorDialog("Error creating notification transaction", e.getMessage());
    }
}
Also used : ECKey(com.sparrowwallet.drongo.crypto.ECKey) SecureString(com.sparrowwallet.drongo.SecureString) SecretPoint(com.sparrowwallet.drongo.bip47.SecretPoint) PaymentCode(com.sparrowwallet.drongo.bip47.PaymentCode) PSBT(com.sparrowwallet.drongo.psbt.PSBT) ElectrumServer(com.sparrowwallet.sparrow.net.ElectrumServer) Storage(com.sparrowwallet.sparrow.io.Storage) TransactionEntry(com.sparrowwallet.sparrow.wallet.TransactionEntry)

Example 8 with PSBT

use of com.sparrowwallet.drongo.psbt.PSBT in project sparrow by sparrowwallet.

the class SendController method broadcastNotification.

public void broadcastNotification(Wallet decryptedWallet) {
    try {
        PaymentCode paymentCode = decryptedWallet.getPaymentCode();
        PaymentCode externalPaymentCode = paymentCodeProperty.get();
        WalletTransaction walletTransaction = walletTransactionProperty.get();
        WalletNode input0Node = walletTransaction.getSelectedUtxos().entrySet().iterator().next().getValue();
        Keystore keystore = input0Node.getWallet().isNested() ? decryptedWallet.getChildWallet(input0Node.getWallet().getName()).getKeystores().get(0) : decryptedWallet.getKeystores().get(0);
        ECKey input0Key = keystore.getKey(input0Node);
        TransactionOutPoint input0Outpoint = walletTransaction.getTransaction().getInputs().iterator().next().getOutpoint();
        SecretPoint secretPoint = new SecretPoint(input0Key.getPrivKeyBytes(), externalPaymentCode.getNotificationKey().getPubKey());
        byte[] blindingMask = PaymentCode.getMask(secretPoint.ECDHSecretAsBytes(), input0Outpoint.bitcoinSerialize());
        byte[] blindedPaymentCode = PaymentCode.blind(paymentCode.getPayload(), blindingMask);
        List<UtxoSelector> utxoSelectors = List.of(new PresetUtxoSelector(walletTransaction.getSelectedUtxos().keySet(), true));
        Long userFee = userFeeSet.get() ? getFeeValueSats() : null;
        double feeRate = getUserFeeRate();
        Integer currentBlockHeight = AppServices.getCurrentBlockHeight();
        boolean groupByAddress = Config.get().isGroupByAddress();
        boolean includeMempoolOutputs = Config.get().isIncludeMempoolOutputs();
        boolean includeSpentMempoolOutputs = includeSpentMempoolOutputsProperty.get();
        WalletTransaction finalWalletTx = decryptedWallet.createWalletTransaction(utxoSelectors, getUtxoFilters(), walletTransaction.getPayments(), List.of(blindedPaymentCode), excludedChangeNodes, feeRate, getMinimumFeeRate(), userFee, currentBlockHeight, groupByAddress, includeMempoolOutputs, includeSpentMempoolOutputs);
        PSBT psbt = finalWalletTx.createPSBT();
        decryptedWallet.sign(psbt);
        decryptedWallet.finalise(psbt);
        Transaction transaction = psbt.extractTransaction();
        ServiceProgressDialog.ProxyWorker proxyWorker = new ServiceProgressDialog.ProxyWorker();
        ElectrumServer.BroadcastTransactionService broadcastTransactionService = new ElectrumServer.BroadcastTransactionService(transaction);
        broadcastTransactionService.setOnSucceeded(successEvent -> {
            ElectrumServer.TransactionMempoolService transactionMempoolService = new ElectrumServer.TransactionMempoolService(walletTransaction.getWallet(), transaction.getTxId(), new HashSet<>(walletTransaction.getSelectedUtxos().values()));
            transactionMempoolService.setDelay(Duration.seconds(2));
            transactionMempoolService.setPeriod(Duration.seconds(5));
            transactionMempoolService.setRestartOnFailure(false);
            transactionMempoolService.setOnSucceeded(mempoolWorkerStateEvent -> {
                Set<String> scriptHashes = transactionMempoolService.getValue();
                if (!scriptHashes.isEmpty()) {
                    transactionMempoolService.cancel();
                    clear(null);
                    if (Config.get().isUsePayNym()) {
                        proxyWorker.setMessage("Finding PayNym...");
                        AppServices.getPayNymService().getPayNym(externalPaymentCode.toString()).subscribe(payNym -> {
                            proxyWorker.end();
                            addChildWallets(walletTransaction.getWallet(), externalPaymentCode, transaction, payNym);
                        }, error -> {
                            proxyWorker.end();
                            addChildWallets(walletTransaction.getWallet(), externalPaymentCode, transaction, null);
                        });
                    } else {
                        proxyWorker.end();
                        addChildWallets(walletTransaction.getWallet(), externalPaymentCode, transaction, null);
                    }
                }
                if (transactionMempoolService.getIterationCount() > 5 && transactionMempoolService.isRunning()) {
                    transactionMempoolService.cancel();
                    proxyWorker.end();
                    log.error("Timeout searching for broadcasted notification transaction");
                    AppServices.showErrorDialog("Timeout searching for broadcasted transaction", "The transaction was broadcast but the server did not register it in the mempool. It is safe to try broadcasting again.");
                }
            });
            transactionMempoolService.setOnFailed(mempoolWorkerStateEvent -> {
                transactionMempoolService.cancel();
                proxyWorker.end();
                log.error("Error searching for broadcasted notification transaction", mempoolWorkerStateEvent.getSource().getException());
                AppServices.showErrorDialog("Timeout searching for broadcasted transaction", "The transaction was broadcast but the server did not register it in the mempool. It is safe to try broadcasting again.");
            });
            proxyWorker.setMessage("Receiving notification transaction...");
            transactionMempoolService.start();
        });
        broadcastTransactionService.setOnFailed(failedEvent -> {
            proxyWorker.end();
            log.error("Error broadcasting notification transaction", failedEvent.getSource().getException());
            AppServices.showErrorDialog("Error broadcasting notification transaction", failedEvent.getSource().getException().getMessage());
        });
        ServiceProgressDialog progressDialog = new ServiceProgressDialog("Broadcast", "Broadcast Notification Transaction", "/image/paynym.png", proxyWorker);
        AppServices.moveToActiveWindowScreen(progressDialog);
        proxyWorker.setMessage("Broadcasting notification transaction...");
        proxyWorker.start();
        broadcastTransactionService.start();
    } catch (Exception e) {
        log.error("Error creating notification transaction", e);
        AppServices.showErrorDialog("Error creating notification transaction", e.getMessage());
    }
}
Also used : ECKey(com.sparrowwallet.drongo.crypto.ECKey) SecureString(com.sparrowwallet.drongo.SecureString) SecretPoint(com.sparrowwallet.drongo.bip47.SecretPoint) PaymentCode(com.sparrowwallet.drongo.bip47.PaymentCode) InvalidAddressException(com.sparrowwallet.drongo.address.InvalidAddressException) IOException(java.io.IOException) PSBT(com.sparrowwallet.drongo.psbt.PSBT) Transaction(com.sparrowwallet.drongo.protocol.Transaction) TransactionOutPoint(com.sparrowwallet.drongo.protocol.TransactionOutPoint)

Example 9 with PSBT

use of com.sparrowwallet.drongo.psbt.PSBT in project drongo by sparrowwallet.

the class Wallet method getSignedKeystores.

/**
 * Determines which keystores have signed a PSBT
 *
 * @param psbt The partially signed or finalized PSBT
 * @return A map keyed with the PSBTInput mapped to a map of the signatures and associated keystores that signed it
 */
public Map<PSBTInput, Map<TransactionSignature, Keystore>> getSignedKeystores(PSBT psbt) {
    Map<PSBTInput, WalletNode> signingNodes = getSigningNodes(psbt);
    Map<PSBTInput, Map<TransactionSignature, Keystore>> signedKeystores = new LinkedHashMap<>();
    for (PSBTInput psbtInput : signingNodes.keySet()) {
        WalletNode walletNode = signingNodes.get(psbtInput);
        Wallet signingWallet = walletNode.getWallet();
        Map<ECKey, Keystore> keystoreKeysForNode = signingWallet.getKeystores().stream().collect(Collectors.toMap(keystore -> signingWallet.getScriptType().getOutputKey(keystore.getPubKey(walletNode)), Function.identity(), (u, v) -> {
            throw new IllegalStateException("Duplicate keys from different keystores for node " + walletNode);
        }, LinkedHashMap::new));
        Map<ECKey, TransactionSignature> keySignatureMap;
        if (psbt.isFinalized() || psbtInput.isTaproot()) {
            keySignatureMap = psbtInput.getSigningKeys(keystoreKeysForNode.keySet());
        } else {
            keySignatureMap = psbtInput.getPartialSignatures();
        }
        keystoreKeysForNode.keySet().retainAll(keySignatureMap.keySet());
        Map<TransactionSignature, Keystore> inputSignatureKeystores = new LinkedHashMap<>();
        for (ECKey signingKey : keystoreKeysForNode.keySet()) {
            inputSignatureKeystores.put(keySignatureMap.get(signingKey), keystoreKeysForNode.get(signingKey));
        }
        signedKeystores.put(psbtInput, inputSignatureKeystores);
    }
    return signedKeystores;
}
Also used : Address(com.sparrowwallet.drongo.address.Address) java.util(java.util) PSBT(com.sparrowwallet.drongo.psbt.PSBT) ChildNumber(com.sparrowwallet.drongo.crypto.ChildNumber) Key(com.sparrowwallet.drongo.crypto.Key) PolicyType(com.sparrowwallet.drongo.policy.PolicyType) Function(java.util.function.Function) Collectors(java.util.stream.Collectors) StandardCharsets(java.nio.charset.StandardCharsets) Policy(com.sparrowwallet.drongo.policy.Policy) PSBTInput(com.sparrowwallet.drongo.psbt.PSBTInput) PSBTOutput(com.sparrowwallet.drongo.psbt.PSBTOutput) com.sparrowwallet.drongo.protocol(com.sparrowwallet.drongo.protocol) ECKey(com.sparrowwallet.drongo.crypto.ECKey) DeterministicKey(com.sparrowwallet.drongo.crypto.DeterministicKey) com.sparrowwallet.drongo(com.sparrowwallet.drongo) ScriptType(com.sparrowwallet.drongo.protocol.ScriptType) PaymentCode(com.sparrowwallet.drongo.bip47.PaymentCode) WITNESS_SCALE_FACTOR(com.sparrowwallet.drongo.protocol.Transaction.WITNESS_SCALE_FACTOR) ECKey(com.sparrowwallet.drongo.crypto.ECKey) PSBTInput(com.sparrowwallet.drongo.psbt.PSBTInput)

Aggregations

PSBT (com.sparrowwallet.drongo.psbt.PSBT)9 SecureString (com.sparrowwallet.drongo.SecureString)5 PSBTInput (com.sparrowwallet.drongo.psbt.PSBTInput)4 BitcoinURI (com.sparrowwallet.drongo.uri.BitcoinURI)4 Address (com.sparrowwallet.drongo.address.Address)3 PaymentCode (com.sparrowwallet.drongo.bip47.PaymentCode)3 ECKey (com.sparrowwallet.drongo.crypto.ECKey)3 com.sparrowwallet.drongo.protocol (com.sparrowwallet.drongo.protocol)3 CryptoPSBT (com.sparrowwallet.hummingbird.registry.CryptoPSBT)3 Storage (com.sparrowwallet.sparrow.io.Storage)3 ElectrumServer (com.sparrowwallet.sparrow.net.ElectrumServer)3 Payjoin (com.sparrowwallet.sparrow.payjoin.Payjoin)3 TransactionEntry (com.sparrowwallet.sparrow.wallet.TransactionEntry)3 StandardCharsets (java.nio.charset.StandardCharsets)3 java.util (java.util)3 Collectors (java.util.stream.Collectors)3 Subscribe (com.google.common.eventbus.Subscribe)2 KeyPurpose (com.sparrowwallet.drongo.KeyPurpose)2 Utils (com.sparrowwallet.drongo.Utils)2 SecretPoint (com.sparrowwallet.drongo.bip47.SecretPoint)2