use of com.sparrowwallet.drongo.crypto.ECKey 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());
}
}
use of com.sparrowwallet.drongo.crypto.ECKey in project sparrow by sparrowwallet.
the class DbPersistence method loadChildWallets.
private Map<WalletAndKey, Storage> loadChildWallets(Storage storage, Wallet masterWallet, ECKey encryptionKey) throws StorageException {
Jdbi jdbi = getJdbi(storage, getFilePassword(encryptionKey));
List<String> schemas = jdbi.withHandle(handle -> {
return handle.createQuery("show schemas").mapTo(String.class).list();
});
List<String> childSchemas = schemas.stream().filter(schema -> schema.startsWith(WALLET_SCHEMA_PREFIX) && !schema.equals(MASTER_SCHEMA)).collect(Collectors.toList());
Map<WalletAndKey, Storage> childWallets = new TreeMap<>();
for (String schema : childSchemas) {
migrate(storage, schema, encryptionKey);
Jdbi childJdbi = getJdbi(storage, getFilePassword(encryptionKey));
Wallet wallet = childJdbi.withHandle(handle -> {
WalletDao walletDao = handle.attach(WalletDao.class);
Wallet childWallet = walletDao.getMainWallet(schema);
childWallet.setName(schema.substring(WALLET_SCHEMA_PREFIX.length()));
childWallet.setMasterWallet(masterWallet);
return childWallet;
});
childWallets.put(new WalletAndKey(wallet, encryptionKey, keyDeriver, Collections.emptyMap()), storage);
}
return childWallets;
}
use of com.sparrowwallet.drongo.crypto.ECKey in project sparrow by sparrowwallet.
the class JsonPersistence method loadWallet.
@Override
public WalletAndKey loadWallet(Storage storage, CharSequence password, ECKey alreadyDerivedKey) throws IOException, StorageException {
Wallet wallet;
ECKey encryptionKey;
try (InputStream fileStream = new FileInputStream(storage.getWalletFile())) {
encryptionKey = getEncryptionKey(password, fileStream, alreadyDerivedKey);
Reader reader = new InputStreamReader(new InflaterInputStream(new ECIESInputStream(fileStream, encryptionKey, getEncryptionMagic())), StandardCharsets.UTF_8);
wallet = gson.fromJson(reader, Wallet.class);
wallet.getPurposeNodes().forEach(purposeNode -> purposeNode.setWallet(wallet));
}
Map<WalletAndKey, Storage> childWallets = loadChildWallets(storage, wallet, encryptionKey);
wallet.setChildWallets(childWallets.keySet().stream().map(WalletAndKey::getWallet).collect(Collectors.toList()));
return new WalletAndKey(wallet, encryptionKey, keyDeriver, childWallets);
}
use of com.sparrowwallet.drongo.crypto.ECKey in project drongo by sparrowwallet.
the class Wallet method finalise.
public void finalise(PSBT psbt) {
int threshold = getDefaultPolicy().getNumSignaturesRequired();
Map<PSBTInput, WalletNode> signingNodes = getSigningNodes(psbt);
for (int i = 0; i < psbt.getTransaction().getInputs().size(); i++) {
TransactionInput txInput = psbt.getTransaction().getInputs().get(i);
PSBTInput psbtInput = psbt.getPsbtInputs().get(i);
if (psbtInput.isFinalized()) {
continue;
}
WalletNode signingNode = signingNodes.get(psbtInput);
// Transaction parent on PSBT utxo might be null in a witness tx, so get utxo tx hash and utxo index from PSBT tx input
TransactionOutput utxo = new TransactionOutput(null, psbtInput.getUtxo().getValue(), psbtInput.getUtxo().getScript()) {
@Override
public Sha256Hash getHash() {
return txInput.getOutpoint().getHash();
}
@Override
public int getIndex() {
return (int) txInput.getOutpoint().getIndex();
}
};
// TODO: Handle taproot scriptpath spending
int signaturesAvailable = psbtInput.isTaproot() ? (psbtInput.getTapKeyPathSignature() != null ? 1 : 0) : psbtInput.getPartialSignatures().size();
if (signaturesAvailable >= threshold && signingNode != null) {
Transaction transaction = new Transaction();
TransactionInput finalizedTxInput;
if (getPolicyType().equals(PolicyType.SINGLE)) {
ECKey pubKey = signingNode.getPubKey();
TransactionSignature transactionSignature = psbtInput.isTaproot() ? psbtInput.getTapKeyPathSignature() : psbtInput.getPartialSignature(pubKey);
if (transactionSignature == null) {
throw new IllegalArgumentException("Pubkey of partial signature does not match wallet pubkey");
}
finalizedTxInput = signingNode.getWallet().getScriptType().addSpendingInput(transaction, utxo, pubKey, transactionSignature);
} else if (getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = signingNode.getPubKeys();
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
for (ECKey pubKey : pubKeys) {
pubKeySignatures.put(pubKey, psbtInput.getPartialSignature(pubKey));
}
List<TransactionSignature> signatures = pubKeySignatures.values().stream().filter(Objects::nonNull).collect(Collectors.toList());
if (signatures.size() < threshold) {
throw new IllegalArgumentException("Pubkeys of partial signatures do not match wallet pubkeys");
}
finalizedTxInput = signingNode.getWallet().getScriptType().addMultisigSpendingInput(transaction, utxo, threshold, pubKeySignatures);
} else {
throw new UnsupportedOperationException("Cannot finalise PSBT for policy type " + getPolicyType());
}
psbtInput.setFinalScriptSig(finalizedTxInput.getScriptSig());
psbtInput.setFinalScriptWitness(finalizedTxInput.getWitness());
psbtInput.clearNonFinalFields();
}
}
psbt.getPsbtOutputs().forEach(PSBTOutput::clearNonFinalFields);
}
use of com.sparrowwallet.drongo.crypto.ECKey in project drongo by sparrowwallet.
the class Wallet method getInputWeightUnits.
/**
* Return the number of weight units required for an input created by this wallet.
*
* @return the number of weight units (WU)
*/
public int getInputWeightUnits() {
// Estimate assuming an input spending from a fresh receive node - it does not matter this node has no real utxos
WalletNode receiveNode = getFreshNode(KeyPurpose.RECEIVE);
Transaction transaction = new Transaction();
TransactionOutput prevTxOut = transaction.addOutput(1L, receiveNode.getAddress());
TransactionInput txInput = null;
if (getPolicyType().equals(PolicyType.SINGLE)) {
ECKey pubKey = receiveNode.getPubKey();
TransactionSignature signature = TransactionSignature.dummy(getScriptType().getSignatureType());
txInput = getScriptType().addSpendingInput(transaction, prevTxOut, pubKey, signature);
} else if (getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = receiveNode.getPubKeys();
int threshold = getDefaultPolicy().getNumSignaturesRequired();
Map<ECKey, TransactionSignature> pubKeySignatures = new TreeMap<>(new ECKey.LexicographicECKeyComparator());
for (int i = 0; i < pubKeys.size(); i++) {
pubKeySignatures.put(pubKeys.get(i), i < threshold ? TransactionSignature.dummy(getScriptType().getSignatureType()) : null);
}
txInput = getScriptType().addMultisigSpendingInput(transaction, prevTxOut, threshold, pubKeySignatures);
}
assert txInput != null;
int wu = txInput.getLength() * WITNESS_SCALE_FACTOR;
if (txInput.hasWitness()) {
wu += txInput.getWitness().getLength();
}
return wu;
}
Aggregations