fixes for encrypted whirlpool wallets and other issues

This commit is contained in:
Craig Raw 2021-08-31 16:19:24 +02:00
parent f30c00ba8f
commit aa10bcfe1a
17 changed files with 142 additions and 69 deletions

2
drongo

@ -1 +1 @@
Subproject commit 7ac4bce14f04163c57b94e34945b5e4a1bf79eb6
Subproject commit 71b5778226ef22881240143425325525c1a98d06

View file

@ -1229,6 +1229,8 @@ public class AppController implements Initializable {
}
}
}
EventManager.get().post(new WalletOpenedEvent(storage, wallet));
}
public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet wallet, Wallet backupWallet) {

View file

@ -968,7 +968,10 @@ public class AppServices {
@Subscribe
public void walletOpening(WalletOpeningEvent event) {
restartBwt(event.getWallet());
}
@Subscribe
public void walletOpened(WalletOpenedEvent event) {
String walletId = event.getStorage().getWalletId(event.getWallet());
Whirlpool whirlpool = whirlpoolMap.get(walletId);
if(whirlpool != null && !whirlpool.isStarted() && isConnected()) {

View file

@ -42,7 +42,13 @@ public class MainApp extends Application {
@Override
public void init() throws Exception {
Thread.setDefaultUncaughtExceptionHandler((t, e) -> LoggerFactory.getLogger(MainApp.class).error("Exception in thread \"" + t.getName() + "\"", e));
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
if(e instanceof IndexOutOfBoundsException && Arrays.stream(e.getStackTrace()).anyMatch(element -> element.getClassName().equals("javafx.scene.chart.BarChart"))) {
LoggerFactory.getLogger(MainApp.class).debug("Exception in thread \"" + t.getName() + "\"", e);;
} else {
LoggerFactory.getLogger(MainApp.class).error("Exception in thread \"" + t.getName() + "\"", e);
}
});
super.init();
}

View file

@ -27,10 +27,10 @@ public class HelpLabel extends Label {
}
private static Glyph getHelpGlyph() {
Glyph lockGlyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.QUESTION_CIRCLE);
lockGlyph.getStyleClass().add("help-icon");
lockGlyph.setFontSize(12);
return lockGlyph;
Glyph glyph = new Glyph("Font Awesome 5 Free Solid", FontAwesome5.Glyph.QUESTION_CIRCLE);
glyph.getStyleClass().add("help-icon");
glyph.setFontSize(11);
return glyph;
}
public final StringProperty helpTextProperty() {

View file

@ -101,7 +101,9 @@ public class UtxosTreeTable extends CoinTreeTable {
public void updateHistory(List<WalletNode> updatedNodes) {
//Utxo entries should have already been updated, so only a resort required
sort();
if(!getRoot().getChildren().isEmpty()) {
sort();
}
}
public void updateLabel(Entry entry) {

View file

@ -0,0 +1,22 @@
package com.sparrowwallet.sparrow.event;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.io.Storage;
public class WalletOpenedEvent {
private final Storage storage;
private final Wallet wallet;
public WalletOpenedEvent(Storage storage, Wallet wallet) {
this.storage = storage;
this.wallet = wallet;
}
public Storage getStorage() {
return storage;
}
public Wallet getWallet() {
return wallet;
}
}

View file

@ -39,6 +39,7 @@ public class FontAwesome5 extends GlyphFont {
HAND_HOLDING_MEDICAL('\ue05c'),
HAND_HOLDING_WATER('\uf4c1'),
HISTORY('\uf1da'),
INFO_CIRCLE('\uf05a'),
KEY('\uf084'),
LAPTOP('\uf109'),
LOCK('\uf023'),

View file

@ -128,7 +128,10 @@ public class SendController extends WalletFormController implements Initializabl
private ToggleButton privacyToggle;
@FXML
private HelpLabel privacyAnalysis;
private HelpLabel optimizationHelp;
@FXML
private Label privacyAnalysis;
@FXML
private Button clearButton;
@ -417,6 +420,9 @@ public class SendController extends WalletFormController implements Initializabl
});
setPreferredOptimizationStrategy();
updatePrivacyAnalysis(null);
optimizationHelp.managedProperty().bind(optimizationHelp.visibleProperty());
privacyAnalysis.managedProperty().bind(privacyAnalysis.visibleProperty());
optimizationHelp.visibleProperty().bind(privacyAnalysis.visibleProperty().not());
createButton.managedProperty().bind(createButton.visibleProperty());
premixButton.managedProperty().bind(premixButton.visibleProperty());
@ -971,11 +977,15 @@ public class SendController extends WalletFormController implements Initializabl
private void updatePrivacyAnalysis(WalletTransaction walletTransaction) {
if(walletTransaction == null) {
privacyAnalysis.setHelpText("Determines whether to optimize the transaction for low fees or greater privacy");
privacyAnalysis.setHelpGraphic(null);
privacyAnalysis.setVisible(false);
privacyAnalysis.setTooltip(null);
} else {
privacyAnalysis.setHelpText("");
privacyAnalysis.setHelpGraphic(new PrivacyAnalysisTooltip(walletTransaction));
privacyAnalysis.setVisible(true);
Tooltip tooltip = new Tooltip();
tooltip.setShowDelay(new Duration(50));
tooltip.setShowDuration(Duration.INDEFINITE);
tooltip.setGraphic(new PrivacyAnalysisTooltip(walletTransaction));
privacyAnalysis.setTooltip(tooltip);
}
}
@ -1012,6 +1022,9 @@ public class SendController extends WalletFormController implements Initializabl
validationSupport.setErrorDecorationEnabled(false);
setInputFieldsDisabled(false);
premixButton.setVisible(false);
createButton.setDefaultButton(true);
}
public UtxoSelector getUtxoSelector() {
@ -1108,35 +1121,8 @@ public class SendController extends WalletFormController implements Initializabl
}
}
Wallet copy = getWalletForm().getWallet().copy();
String walletId = walletForm.getWalletId();
if(copy.isEncrypted()) {
WalletPasswordDialog dlg = new WalletPasswordDialog(copy.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
Storage.DecryptWalletService decryptWalletService = new Storage.DecryptWalletService(copy, password.get());
decryptWalletService.setOnSucceeded(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
Wallet decryptedWallet = decryptWalletService.getValue();
broadcastPremixUnencrypted(decryptedWallet);
});
decryptWalletService.setOnFailed(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
AppServices.showErrorDialog("Incorrect Password", decryptWalletService.getException().getMessage());
});
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
decryptWalletService.start();
}
} else {
broadcastPremixUnencrypted(copy);
}
}
public void broadcastPremixUnencrypted(Wallet decryptedWallet) {
//The WhirlpoolWallet has already been configured for the tx0 preview
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWalletId());
whirlpool.setScode(Config.get().getScode());
whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet);
Map<BlockTransactionHashIndex, WalletNode> utxos = walletTransactionProperty.get().getSelectedUtxos();
Whirlpool.Tx0BroadcastService tx0BroadcastService = new Whirlpool.Tx0BroadcastService(whirlpool, whirlpoolProperty.get(), utxos.keySet());
tx0BroadcastService.setOnRunning(workerStateEvent -> {
@ -1146,12 +1132,10 @@ public class SendController extends WalletFormController implements Initializabl
tx0BroadcastService.setOnSucceeded(workerStateEvent -> {
premixButton.setDisable(false);
Sha256Hash txid = tx0BroadcastService.getValue();
decryptedWallet.clearPrivate();
clear(null);
});
tx0BroadcastService.setOnFailed(workerStateEvent -> {
premixButton.setDisable(false);
decryptedWallet.clearPrivate();
Throwable exception = workerStateEvent.getSource().getException();
while(exception.getCause() != null) {
exception = exception.getCause();

View file

@ -6,8 +6,10 @@ import com.samourai.whirlpool.client.tx0.Tx0Preview;
import com.sparrowwallet.drongo.BitcoinUnit;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.SecureString;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.crypto.*;
import com.sparrowwallet.drongo.protocol.Transaction;
import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.AppServices;
@ -15,6 +17,7 @@ import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.whirlpool.Whirlpool;
import com.sparrowwallet.sparrow.whirlpool.WhirlpoolDialog;
import javafx.application.Platform;
@ -160,17 +163,70 @@ public class UtxosController extends WalletFormController implements Initializab
List<UtxoEntry> selectedEntries = getSelectedUtxos();
WhirlpoolDialog whirlpoolDialog = new WhirlpoolDialog(getWalletForm().getWalletId(), getWalletForm().getWallet(), selectedEntries);
Optional<Tx0Preview> optTx0Preview = whirlpoolDialog.showAndWait();
optTx0Preview.ifPresent(tx0Preview -> previewPremixTransaction(getWalletForm().getWallet(), tx0Preview, selectedEntries));
optTx0Preview.ifPresent(tx0Preview -> previewPremix(tx0Preview, selectedEntries));
}
public void previewPremixTransaction(Wallet wallet, Tx0Preview tx0Preview, List<UtxoEntry> utxoEntries) {
public void previewPremix(Tx0Preview tx0Preview, List<UtxoEntry> utxoEntries) {
Wallet wallet = getWalletForm().getWallet();
String walletId = walletForm.getWalletId();
if(!wallet.isWhirlpoolMasterWallet() && wallet.isEncrypted()) {
WalletPasswordDialog dlg = new WalletPasswordDialog(wallet.getMasterName(), WalletPasswordDialog.PasswordRequirement.LOAD);
Optional<SecureString> password = dlg.showAndWait();
if(password.isPresent()) {
Storage.KeyDerivationService keyDerivationService = new Storage.KeyDerivationService(walletForm.getStorage(), password.get());
keyDerivationService.setOnSucceeded(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Done"));
ECKey encryptionFullKey = keyDerivationService.getValue();
Key key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2);
wallet.decrypt(key);
try {
prepareWhirlpoolWallet(wallet);
} finally {
wallet.encrypt(key);
for(Wallet childWallet : wallet.getChildWallets()) {
if(!childWallet.isEncrypted()) {
childWallet.encrypt(key);
}
}
key.clear();
encryptionFullKey.clear();
password.get().clear();
}
previewPremix(wallet, tx0Preview, utxoEntries);
});
keyDerivationService.setOnFailed(workerStateEvent -> {
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.END, "Failed"));
AppServices.showErrorDialog("Incorrect Password", keyDerivationService.getException().getMessage());
});
EventManager.get().post(new StorageEvent(walletId, TimedEvent.Action.START, "Decrypting wallet..."));
keyDerivationService.start();
}
} else {
if(!wallet.isWhirlpoolMasterWallet()) {
prepareWhirlpoolWallet(wallet);
}
previewPremix(wallet, tx0Preview, utxoEntries);
}
}
private void prepareWhirlpoolWallet(Wallet decryptedWallet) {
Whirlpool whirlpool = AppServices.get().getWhirlpool(getWalletForm().getWalletId());
whirlpool.setScode(Config.get().getScode());
whirlpool.setHDWallet(getWalletForm().getWalletId(), decryptedWallet);
for(StandardAccount whirlpoolAccount : StandardAccount.WHIRLPOOL_ACCOUNTS) {
if(wallet.getChildWallet(whirlpoolAccount) == null) {
Wallet childWallet = wallet.addChildWallet(whirlpoolAccount);
EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), wallet, childWallet));
if(decryptedWallet.getChildWallet(whirlpoolAccount) == null) {
Wallet childWallet = decryptedWallet.addChildWallet(whirlpoolAccount);
EventManager.get().post(new ChildWalletAddedEvent(getWalletForm().getStorage(), decryptedWallet, childWallet));
}
}
}
private void previewPremix(Wallet wallet, Tx0Preview tx0Preview, List<UtxoEntry> utxoEntries) {
Wallet premixWallet = wallet.getChildWallet(StandardAccount.WHIRLPOOL_PREMIX);
Wallet badbankWallet = wallet.getChildWallet(StandardAccount.WHIRLPOOL_BADBANK);

View file

@ -67,7 +67,7 @@ public class Whirlpool {
private HD_Wallet hdWallet;
private String walletId;
private BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
private final BooleanProperty mixingProperty = new SimpleBooleanProperty(false);
public Whirlpool(Network network, HostAndPort torProxy, String sCode) {
this.torProxy = torProxy;
@ -134,14 +134,8 @@ public class Whirlpool {
}
private Tx0ParamService getTx0ParamService() {
try {
SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance();
return new Tx0ParamService(minerFeeSupplier, config);
} catch(Exception e) {
log.error("Error fetching miner fees", e);
}
return null;
SparrowMinerFeeSupplier minerFeeSupplier = SparrowMinerFeeSupplier.getInstance();
return new Tx0ParamService(minerFeeSupplier, config);
}
public void setHDWallet(String walletId, Wallet wallet) {

View file

@ -9,8 +9,8 @@ import com.samourai.whirlpool.client.wallet.data.walletState.WalletStateSupplier
import com.sparrowwallet.sparrow.whirlpool.dataSource.SparrowWalletStateSupplier;
public class SparrowDataPersister implements DataPersister {
private WalletStateSupplier walletStateSupplier;
private UtxoConfigSupplier utxoConfigSupplier;
private final WalletStateSupplier walletStateSupplier;
private final UtxoConfigSupplier utxoConfigSupplier;
public SparrowDataPersister(WhirlpoolWallet whirlpoolWallet) throws Exception {
WhirlpoolWalletConfig config = whirlpoolWallet.getConfig();

View file

@ -4,8 +4,8 @@ import com.samourai.wallet.client.indexHandler.AbstractIndexHandler;
import com.sparrowwallet.drongo.wallet.WalletNode;
public class SparrowIndexHandler extends AbstractIndexHandler {
private WalletNode walletNode;
private int defaultValue;
private final WalletNode walletNode;
private final int defaultValue;
public SparrowIndexHandler(WalletNode walletNode) {
this(walletNode, 0);
@ -19,8 +19,7 @@ public class SparrowIndexHandler extends AbstractIndexHandler {
@Override
public synchronized int get() {
Integer currentIndex = walletNode.getHighestUsedIndex();
int nextIndex = currentIndex == null ? defaultValue : currentIndex + 1;
return nextIndex;
return currentIndex == null ? defaultValue : currentIndex + 1;
}
@Override

View file

@ -15,13 +15,13 @@ import java.util.LinkedHashMap;
import java.util.Map;
public class SparrowWalletStateSupplier implements WalletStateSupplier {
private String walletId;
private Map<String, IIndexHandler> indexHandlerWallets;
private final String walletId;
private final Map<String, IIndexHandler> indexHandlerWallets;
// private int externalIndexDefault;
public SparrowWalletStateSupplier(String walletId, ExternalDestination externalDestination) throws Exception {
this.walletId = walletId;
this.indexHandlerWallets = new LinkedHashMap();
this.indexHandlerWallets = new LinkedHashMap<>();
// this.externalIndexDefault = externalDestination != null ? externalDestination.getStartIndex() : 0;
}

View file

@ -1,2 +1 @@
drop table if exists utxoMixData;
create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null);
create table utxoMixData (id identity not null, hash binary(32) not null, mixesDone integer not null default 0, expired bigint, wallet bigint not null);

View file

@ -176,7 +176,12 @@
</ToggleButton>
</buttons>
</SegmentedButton>
<HelpLabel fx:id="privacyAnalysis" />
<HelpLabel fx:id="optimizationHelp" helpText="Determines whether to optimize the transaction for low fees or greater privacy" />
<Label fx:id="privacyAnalysis" graphicTextGap="5" text="Analysis..." styleClass="help-label">
<graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="11" icon="INFO_CIRCLE" />
</graphic>
</Label>
</HBox>
<HBox AnchorPane.rightAnchor="10">
<Button fx:id="clearButton" text="Clear" cancelButton="true" onAction="#clear" />

View file

@ -100,7 +100,7 @@
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="RANDOM" styleClass="title-icon" />
</graphic>
</Label>
<Label text="Choose which pool to use below. You will then be able to preview your premix transaction." wrapText="true" styleClass="content-text" />
<Label text="Choose which pool to use below. You will then be able to preview your premix transaction. Your wallet password may be required to add the premix wallet." wrapText="true" styleClass="content-text" />
<HBox spacing="20" alignment="CENTER_LEFT">
<padding>
<Insets top="20" bottom="5" />