Compare commits

...
11 Commits
14 changed files with 97 additions and 22 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ plugins {
id 'org.beryx.jlink' version '2.26.0'
}
def sparrowVersion = '1.7.4'
def sparrowVersion = '1.7.6'
def os = org.gradle.internal.os.OperatingSystem.current()
def osName = os.getFamilyName()
if(os.macOsX) {
+1 -1
View File
@@ -82,7 +82,7 @@ sudo apt install -y rpm fakeroot binutils
First, assign a temporary variable in your shell for the specific release you want to build. For the current one specify:
```shell
GIT_TAG="1.7.3"
GIT_TAG="1.7.5"
```
The project can then be initially cloned as follows:
+1 -1
View File
@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.7.4</string>
<string>1.7.6</string>
<key>CFBundleSignature</key>
<string>????</string>
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
@@ -18,7 +18,7 @@ import java.util.*;
public class SparrowWallet {
public static final String APP_ID = "com.sparrowwallet.sparrow";
public static final String APP_NAME = "Sparrow";
public static final String APP_VERSION = "1.7.4";
public static final String APP_VERSION = "1.7.6";
public static final String APP_VERSION_SUFFIX = "";
public static final String APP_HOME_PROPERTY = "sparrow.home";
public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK";
@@ -33,6 +33,8 @@ import javax.smartcardio.CardException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static com.sparrowwallet.sparrow.io.CardApi.isReaderAvailable;
public class CardImportPane extends TitledDescriptionPane {
private static final Logger log = LoggerFactory.getLogger(CardImportPane.class);
@@ -62,6 +64,12 @@ public class CardImportPane extends TitledDescriptionPane {
}
private void importCard() {
if(!isReaderAvailable()) {
setError("No reader", "No card reader was detected.");
importButton.setDisable(false);
return;
}
if(pin.get().length() < 6) {
setDescription(pin.get().isEmpty() ? "Enter PIN code" : "PIN code too short");
setContent(getPinEntry());
@@ -10,6 +10,7 @@ import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.Event;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
@@ -40,13 +41,15 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
private final BooleanProperty initializedProperty = new SimpleBooleanProperty(false);
private final BooleanProperty wordsSelectedProperty = new SimpleBooleanProperty(false);
private final List<TablePosition> selectedCells = new ArrayList<>();
public MnemonicGridDialog() {
DialogPane dialogPane = new MnemonicGridDialogPane();
setDialogPane(dialogPane);
setTitle("Border Wallets Grid");
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("grid.css").toExternalForm());
dialogPane.setHeaderText("Load a Border Wallets PDF, or generate a grid from a BIP39 seed.\nThen select 11 or 23 words in a pattern on the grid. Note the order of selection is important!");
dialogPane.setHeaderText("Load a Border Wallets PDF, or generate a grid from a BIP39 seed.\nThen select 11 or 23 words in a pattern on the grid, one at a time (do not drag).\nThe order of selection is important!");
javafx.scene.image.Image image = new Image("/image/border-wallets.png");
dialogPane.setGraphic(new ImageView(image));
@@ -71,6 +74,15 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
spreadsheetView.getSelectionModel().getSelectedCells().addListener(new ListChangeListener<>() {
@Override
public void onChanged(Change<? extends TablePosition> c) {
while(c.next()) {
if(c.wasRemoved()) {
selectedCells.removeAll(c.getRemoved());
}
if(c.wasAdded()) {
selectedCells.addAll(c.getAddedSubList());
}
}
int numWords = c.getList().size();
wordsSelectedProperty.set(numWords == 11 || numWords == 23);
}
@@ -93,7 +105,7 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
final ButtonType loadCsvButtonType = new javafx.scene.control.ButtonType("Load PDF...", ButtonBar.ButtonData.LEFT);
dialogPane.getButtonTypes().add(loadCsvButtonType);
final ButtonType generateButtonType = new javafx.scene.control.ButtonType("Generate Grid...", ButtonBar.ButtonData.HELP);
final ButtonType generateButtonType = new javafx.scene.control.ButtonType("Generate Grid...", ButtonBar.ButtonData.HELP_2);
dialogPane.getButtonTypes().add(generateButtonType);
final ButtonType clearButtonType = new javafx.scene.control.ButtonType("Clear Selection", ButtonBar.ButtonData.OTHER);
@@ -137,7 +149,7 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
}
private List<String> getSelectedWords() {
List<String> abbreviations = spreadsheetView.getSelectionModel().getSelectedCells().stream()
List<String> abbreviations = selectedCells.stream()
.map(position -> (String)spreadsheetView.getGrid().getRows().get(position.getRow()).get(position.getColumn()).getItem()).collect(Collectors.toList());
List<String> words = new ArrayList<>();
@@ -148,6 +160,13 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
break;
}
}
try {
int index = Integer.parseInt(abbreviation);
words.add(Bip39MnemonicCode.INSTANCE.getWordList().get(index - 1));
} catch(NumberFormatException e) {
//ignore
}
}
if(words.size() != abbreviations.size()) {
@@ -217,6 +236,8 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
try(BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
String[][] wordGrid = PdfUtils.getWordGrid(inputStream);
spreadsheetView.setGrid(getGrid(wordGrid));
selectedCells.clear();
spreadsheetView.getSelectionModel().clearSelection();
initializedProperty.set(true);
} catch(Exception e) {
AppServices.showErrorDialog("Cannot load PDF", e.getMessage());
@@ -225,18 +246,20 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
});
button = loadButton;
} else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP) {
} else if(buttonType.getButtonData() == ButtonBar.ButtonData.HELP_2) {
Button generateButton = new Button(buttonType.getText());
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
ButtonBar.setButtonData(generateButton, buttonData);
generateButton.setOnAction(event -> {
SeedEntryDialog seedEntryDialog = new SeedEntryDialog(12);
SeedEntryDialog seedEntryDialog = new SeedEntryDialog("Border Wallets Entropy Grid Recovery Seed", 12);
Optional<List<String>> optWords = seedEntryDialog.showAndWait();
if(optWords.isPresent()) {
List<String> mnemonicWords = optWords.get();
List<String> shuffledWordList = shuffle(mnemonicWords);
String[][] wordGrid = toGrid(shuffledWordList);
spreadsheetView.setGrid(getGrid(wordGrid));
selectedCells.clear();
spreadsheetView.getSelectionModel().clearSelection();
initializedProperty.set(true);
if(seedEntryDialog.isGenerated()) {
@@ -251,6 +274,7 @@ public class MnemonicGridDialog extends Dialog<List<String>> {
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
ButtonBar.setButtonData(clearButton, buttonData);
clearButton.setOnAction(event -> {
selectedCells.clear();
spreadsheetView.getSelectionModel().clearSelection();
});
@@ -18,8 +18,8 @@ public class MnemonicKeystoreEntryPane extends MnemonicKeystorePane {
private boolean generated;
public MnemonicKeystoreEntryPane(int numWords) {
super(DeterministicSeed.Type.BIP39.getName(), "Enter seed words", "", "image/" + WalletModel.SEED.getType() + ".png");
public MnemonicKeystoreEntryPane(String name, int numWords) {
super(name, "Enter seed words", "", "image/" + WalletModel.SEED.getType() + ".png");
showHideLink.setVisible(false);
buttonBox.getChildren().clear();
@@ -100,6 +100,7 @@ public class MnemonicKeystorePane extends TitledDescriptionPane {
Optional<List<String>> optWords = mnemonicGridDialog.showAndWait();
if(optWords.isPresent()) {
List<String> words = optWords.get();
defaultWordSizeProperty.set(words.size() + 1);
setContent(getMnemonicWordsEntry(words.size() + 1, true, true));
setExpanded(true);
@@ -12,7 +12,7 @@ import java.util.List;
public class SeedEntryDialog extends Dialog<List<String>> {
private final MnemonicKeystoreEntryPane keystorePane;
public SeedEntryDialog(int numWords) {
public SeedEntryDialog(String name, int numWords) {
final DialogPane dialogPane = new MnemonicGridDialogPane();
setDialogPane(dialogPane);
dialogPane.getStylesheets().add(AppServices.class.getResource("general.css").toExternalForm());
@@ -38,7 +38,7 @@ public class SeedEntryDialog extends Dialog<List<String>> {
Accordion keystoreAccordion = new Accordion();
scrollPane.setContent(keystoreAccordion);
keystorePane = new MnemonicKeystoreEntryPane(numWords);
keystorePane = new MnemonicKeystoreEntryPane(name, numWords);
keystorePane.setAnimated(false);
keystoreAccordion.getPanes().add(keystorePane);
@@ -210,6 +210,8 @@ public class PdfUtils {
log.error("Error creating word grid PDF", e);
AppServices.showErrorDialog("Error creating word grid PDF", e.getMessage());
}
} else {
AppServices.showWarningDialog("Entropy Grid PDF not saved", "You have chosen to not save the entropy grid PDF.\n\nDo not store funds on a seed selected from this grid - you will not be able to regenerate it!");
}
}
}
@@ -1,7 +1,6 @@
package com.sparrowwallet.sparrow.io;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.*;
import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.wallet.*;
import org.slf4j.Logger;
@@ -12,6 +11,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class SpecterDesktop implements WalletImport, WalletExport {
@@ -54,7 +54,44 @@ public class SpecterDesktop implements WalletImport, WalletExport {
public Wallet importWallet(InputStream inputStream, String password) throws ImportException {
try {
Gson gson = new Gson();
SpecterWallet specterWallet = gson.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), SpecterWallet.class);
JsonObject jsonObj = gson.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonElement.class).getAsJsonObject();
SpecterWallet specterWallet = new SpecterWallet();
if(jsonObj.get("descriptor") != null) {
specterWallet.descriptor = jsonObj.get("descriptor").getAsString();
} else if(jsonObj.get("recv_descriptor") != null) {
specterWallet.descriptor = jsonObj.get("recv_descriptor").getAsString();
}
if(jsonObj.get("label") != null) {
specterWallet.label = jsonObj.get("label").getAsString();
} else if(jsonObj.get("name") != null) {
specterWallet.label = jsonObj.get("name").getAsString();
}
if(jsonObj.get("blockheight") != null) {
specterWallet.blockheight = jsonObj.get("blockheight").getAsInt();
}
if(jsonObj.get("devices") != null) {
JsonArray jsonDevices = jsonObj.get("devices").getAsJsonArray();
specterWallet.devices = new ArrayList<>();
for(JsonElement jsonDevice : jsonDevices) {
SpecterWalletDevice specterWalletDevice = new SpecterWalletDevice();
if(jsonDevice.isJsonObject()) {
JsonObject jsonDeviceObj = (JsonObject)jsonDevice;
if(jsonDeviceObj.get("label") != null) {
specterWalletDevice.label = jsonDeviceObj.get("label").getAsString();
}
if(jsonDeviceObj.get("type") != null) {
specterWalletDevice.type = jsonDeviceObj.get("type").getAsString();
}
} else if(jsonDevice.isJsonPrimitive()) {
specterWalletDevice.label = jsonDevice.getAsString();
}
specterWallet.devices.add(specterWalletDevice);
}
}
if(specterWallet.descriptor != null) {
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(specterWallet.descriptor);
@@ -80,6 +117,9 @@ public class SpecterDesktop implements WalletImport, WalletExport {
} else {
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
}
} else {
keystore.setWalletModel(WalletModel.SPARROW);
keystore.setSource(KeystoreSource.SW_WATCH);
}
}
}
@@ -15,8 +15,6 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.sparrowwallet.sparrow.io.CardApi.isReaderAvailable;
public class HwAirgappedController extends KeystoreImportDetailController {
private static final Logger log = LoggerFactory.getLogger(HwAirgappedController.class);
@@ -40,10 +38,7 @@ public class HwAirgappedController extends KeystoreImportDetailController {
}
}
List<KeystoreCardImport> cardImporters = Collections.emptyList();
if(isReaderAvailable()) {
cardImporters = List.of(new Tapsigner());
}
List<KeystoreCardImport> cardImporters = List.of(new Tapsigner());
for(KeystoreCardImport importer : cardImporters) {
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
CardImportPane importPane = new CardImportPane(getMasterController().getWallet(), importer, getMasterController().getRequiredDerivation());
@@ -56,6 +56,8 @@ public enum BroadcastSource {
return new URL(getBaseUrl(proxy) + "/api/tx");
} else if(Network.get() == Network.TESTNET) {
return new URL(getBaseUrl(proxy) + "/testnet/api/tx");
} else if(Network.get() == Network.SIGNET) {
return new URL(getBaseUrl(proxy) + "/signet/api/tx");
} else {
throw new IllegalStateException("Cannot broadcast transaction to " + getName() + " on network " + Network.get());
}
@@ -125,6 +125,9 @@ public class BitcoindClient {
}
ListWalletDirResult listWalletDirResult = getBitcoindService().listWalletDir();
if(listWalletDirResult == null) {
throw new RuntimeException("Wallet support must be enabled in Bitcoin Core");
}
boolean exists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(CORE_WALLET_NAME));
legacyWalletExists = listWalletDirResult.wallets().stream().anyMatch(walletDirResult -> walletDirResult.name().equals(Bwt.DEFAULT_CORE_WALLET));