mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-07-30 19:46:16 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d16c87b40 | ||
|
|
b59a65dcfe | ||
|
|
87cc28e0a4 | ||
|
|
1187925543 | ||
|
|
cd4edab4ae | ||
|
|
daf320f36b | ||
|
|
f6ff92865b | ||
|
|
d420c71673 | ||
|
|
07101b3ca0 | ||
|
|
00f7f3f5b3 | ||
|
|
5d2c133401 | ||
|
|
7b0dfd66a7 | ||
|
|
83719e7df2 | ||
|
|
f1b246f0b0 |
@@ -64,10 +64,12 @@ Usage: sparrow [options]
|
||||
Possible Values: [ERROR, WARN, INFO, DEBUG, TRACE]
|
||||
--network, -n
|
||||
Network to use
|
||||
Possible Values: [mainnet, testnet, regtest, signet]
|
||||
Possible Values: [mainnet, testnet, regtest, signet, testnet4]
|
||||
```
|
||||
|
||||
As a fallback, the network (mainnet, testnet, regtest or signet) can also be set using an environment variable `SPARROW_NETWORK`. For example:
|
||||
Note that testnet currently refers to testnet3.
|
||||
|
||||
As a fallback, the network (mainnet, testnet, testnet4, regtest or signet) can also be set using an environment variable `SPARROW_NETWORK`. For example:
|
||||
|
||||
`export SPARROW_NETWORK=testnet`
|
||||
|
||||
@@ -83,7 +85,7 @@ When not explicitly configured using the command line argument above, Sparrow st
|
||||
| Linux | ~/.sparrow |
|
||||
| Windows | %APPDATA%/Sparrow |
|
||||
|
||||
Testnet, regtest and signet configurations (along with their wallets) are stored in subfolders to allow easy switching between networks.
|
||||
Testnet3, testnet4, regtest and signet configurations (along with their wallets) are stored in subfolders to allow easy switching between networks.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ plugins {
|
||||
id 'org.beryx.jlink' version '3.0.1'
|
||||
}
|
||||
|
||||
def sparrowVersion = '1.9.0'
|
||||
def sparrowVersion = '1.9.1'
|
||||
def os = org.gradle.internal.os.OperatingSystem.current()
|
||||
def osName = os.getFamilyName()
|
||||
if(os.macOsX) {
|
||||
|
||||
@@ -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.8.6"
|
||||
GIT_TAG="1.9.0"
|
||||
```
|
||||
|
||||
The project can then be initially cloned as follows:
|
||||
|
||||
+1
-1
Submodule drongo updated: 143d28166a...571db32574
@@ -21,7 +21,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.9.0</string>
|
||||
<string>1.9.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<!-- See https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
|
||||
|
||||
@@ -391,7 +391,7 @@ public class AppController implements Initializable {
|
||||
MenuItem homeItem = new MenuItem("Home Folder...");
|
||||
homeItem.setOnAction(this::restartInHome);
|
||||
restart.getItems().add(homeItem);
|
||||
List<Network> networks = new ArrayList<>(List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET));
|
||||
List<Network> networks = new ArrayList<>(List.of(Network.MAINNET, Network.TESTNET, Network.TESTNET4, Network.SIGNET));
|
||||
networks.remove(Network.get());
|
||||
for(Network network : networks) {
|
||||
MenuItem networkItem = new MenuItem(network.toDisplayString());
|
||||
|
||||
@@ -18,7 +18,7 @@ import java.util.*;
|
||||
public class SparrowWallet {
|
||||
public static final String APP_ID = "sparrow";
|
||||
public static final String APP_NAME = "Sparrow";
|
||||
public static final String APP_VERSION = "1.9.0";
|
||||
public static final String APP_VERSION = "1.9.1";
|
||||
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";
|
||||
@@ -66,6 +66,11 @@ public class SparrowWallet {
|
||||
Network.set(Network.TESTNET);
|
||||
}
|
||||
|
||||
File testnet4Flag = new File(Storage.getSparrowHome(), "network-" + Network.TESTNET4.getName());
|
||||
if(testnet4Flag.exists()) {
|
||||
Network.set(Network.TESTNET4);
|
||||
}
|
||||
|
||||
File signetFlag = new File(Storage.getSparrowHome(), "network-" + Network.SIGNET.getName());
|
||||
if(signetFlag.exists()) {
|
||||
Network.set(Network.SIGNET);
|
||||
|
||||
@@ -3,13 +3,14 @@ package com.sparrowwallet.sparrow.control;
|
||||
import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.PdfUtils;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQR;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.Button;
|
||||
|
||||
public class DescriptorQRDisplayDialog extends QRDisplayDialog {
|
||||
public DescriptorQRDisplayDialog(String walletName, String outputDescriptor, UR ur) {
|
||||
super(ur);
|
||||
public DescriptorQRDisplayDialog(String walletName, String outputDescriptor, UR ur, BBQR bbqr, boolean selectBbqrButton) {
|
||||
super(ur, bbqr, false, false, selectBbqrButton);
|
||||
|
||||
DialogPane dialogPane = getDialogPane();
|
||||
final ButtonType pdfButtonType = new javafx.scene.control.ButtonType("Save PDF...", ButtonBar.ButtonData.HELP_2);
|
||||
@@ -19,7 +20,7 @@ public class DescriptorQRDisplayDialog extends QRDisplayDialog {
|
||||
pdfButton.setGraphicTextGap(5);
|
||||
pdfButton.setGraphic(getGlyph(FontAwesome5.Glyph.FILE_PDF));
|
||||
pdfButton.addEventFilter(ActionEvent.ACTION, event -> {
|
||||
PdfUtils.saveOutputDescriptor(walletName, outputDescriptor, ur);
|
||||
PdfUtils.saveOutputDescriptor(walletName, outputDescriptor, ur, isUseBbqrEncoding() ? bbqr : null);
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -171,11 +171,18 @@ public class FileWalletExportPane extends TitledDescriptionPane {
|
||||
} else if(exporter instanceof Bip129) {
|
||||
UR ur = UR.fromBytes(outputStream.toByteArray());
|
||||
BBQR bbqr = new BBQR(BBQRType.UNICODE, outputStream.toByteArray());
|
||||
qrDisplayDialog = new QRDisplayDialog(ur, bbqr, false, true, false);
|
||||
qrDisplayDialog = new QRDisplayDialog(ur, bbqr, false, false, false);
|
||||
} else if(exporter instanceof Descriptor) {
|
||||
boolean addBbqrOption = exportWallet.getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().showBbqr());
|
||||
boolean selectBbqrOption = exportWallet.getKeystores().stream().allMatch(keystore -> keystore.getWalletModel().selectBbqr());
|
||||
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(exportWallet, KeyPurpose.DEFAULT_PURPOSES, null);
|
||||
CryptoOutput cryptoOutput = getCryptoOutput(exportWallet);
|
||||
qrDisplayDialog = new DescriptorQRDisplayDialog(exportWallet.getFullDisplayName(), outputDescriptor.toString(true), cryptoOutput.toUR());
|
||||
BBQR bbqr = addBbqrOption ? new BBQR(BBQRType.UNICODE, outputDescriptor.toString(true).getBytes(StandardCharsets.UTF_8)) : null;
|
||||
qrDisplayDialog = new DescriptorQRDisplayDialog(exportWallet.getFullDisplayName(), outputDescriptor.toString(true), cryptoOutput.toUR(), bbqr, selectBbqrOption);
|
||||
} else if(exporter.getClass().equals(ColdcardMultisig.class)) {
|
||||
UR ur = UR.fromBytes(outputStream.toByteArray());
|
||||
BBQR bbqr = new BBQR(BBQRType.UNICODE, outputStream.toByteArray());
|
||||
qrDisplayDialog = new QRDisplayDialog(ur, bbqr, false, false, true);
|
||||
} else {
|
||||
qrDisplayDialog = new QRDisplayDialog(outputStream.toString(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@@ -259,6 +259,10 @@ public class QRDisplayDialog extends Dialog<ButtonType> {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isUseBbqrEncoding() {
|
||||
return useBbqrEncoding;
|
||||
}
|
||||
|
||||
private void setUseBbqrEncoding(boolean useBbqrEncoding) {
|
||||
if(useBbqrEncoding) {
|
||||
this.useBbqrEncoding = true;
|
||||
|
||||
@@ -28,7 +28,7 @@ public class CaravanMultisig implements WalletImport, WalletExport {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Unchained or Caravan Multisig";
|
||||
return "Unchained Caravan Multisig";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,7 +56,7 @@ public class CaravanMultisig implements WalletImport, WalletExport {
|
||||
Keystore keystore = new Keystore(extKey.name.length() > Keystore.MAX_LABEL_LENGTH ? extKey.name.substring(0, Keystore.MAX_LABEL_LENGTH) : extKey.name);
|
||||
|
||||
if("Unknown".equals(extKey.bip32Path)) {
|
||||
extKey.bip32Path = "m/45'/0/0/0";
|
||||
extKey.bip32Path = "m/0/0/0/0";
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -107,7 +107,7 @@ public class CaravanMultisig implements WalletImport, WalletExport {
|
||||
CaravanFile cf = new CaravanFile();
|
||||
cf.name = wallet.getFullName();
|
||||
cf.addressType = wallet.getScriptType().toString().replace('-', '_');
|
||||
cf.network = Network.get().getName();
|
||||
cf.network = Network.getCanonical().getName();
|
||||
cf.client = new Client();
|
||||
|
||||
Quorum quorum = new Quorum();
|
||||
|
||||
@@ -7,10 +7,7 @@ import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.policy.Policy;
|
||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -111,7 +108,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
|
||||
@Override
|
||||
public String getKeystoreImportDescription(int account) {
|
||||
return "Import file created by using the Settings > Multisig Wallets > Export XPUB > " + account + " feature on your Coldcard.";
|
||||
return "Import file or QR created by using Advanced/Tools > Export Wallet > Sparrow Wallet" + (account > 0 ? " > 1 > " + account : "") + " on your Coldcard. For older firmware use Settings > Multisig Wallets > Export XPUB > " + account + ".";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,8 +171,10 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
wallet.setDefaultPolicy(policy);
|
||||
wallet.setScriptType(scriptType);
|
||||
|
||||
if(!wallet.isValid()) {
|
||||
throw new IllegalStateException("This file does not describe a valid wallet. " + getKeystoreImportDescription());
|
||||
try {
|
||||
wallet.checkWallet();
|
||||
} catch(InvalidWalletException e) {
|
||||
throw new IllegalStateException("This file does not describe a valid wallet: " + e.getMessage());
|
||||
}
|
||||
|
||||
return wallet;
|
||||
@@ -186,7 +185,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
|
||||
@Override
|
||||
public String getWalletImportDescription() {
|
||||
return "Import file created by using the Settings > Multisig Wallets > [Wallet Detail] > Coldcard Export feature on your Coldcard.";
|
||||
return "Import file or QR created by using Settings > Multisig Wallets > [Wallet Detail] > Coldcard Export on your Coldcard.";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,7 +238,7 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||
|
||||
@Override
|
||||
public String getWalletExportDescription() {
|
||||
return "Export file that can be read by your Coldcard using the Settings > Multisig Wallets > Import from File feature.";
|
||||
return "Export file or QR that can be read by your Coldcard using Settings > Multisig Wallets > Import from File or QR.";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -30,7 +30,7 @@ public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
|
||||
|
||||
@Override
|
||||
public String getKeystoreImportDescription(int account) {
|
||||
return "Import file created by using the Advanced > MicroSD > Export Wallet > Generic JSON > " + account + " feature on your Coldcard. Note this requires firmware version 3.1.3 or later.";
|
||||
return "Import file or QR created by using Advanced/Tools > Export Wallet > Sparrow Wallet" + (account > 0 ? " > 1 > " + account : "") + " on your Coldcard. For older firmware use Advanced > MicroSD > Export Wallet > Generic JSON > " + account + ".";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -328,8 +328,12 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||
ek.derivation = keystore.getKeyDerivation().getDerivationPath();
|
||||
ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint();
|
||||
ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader);
|
||||
ek.type = "hardware";
|
||||
ek.hw_type = keystore.getWalletModel().getType();
|
||||
if(keystore.getWalletModel().hasUsb()) {
|
||||
ek.type = "hardware";
|
||||
ek.hw_type = keystore.getWalletModel().getType();
|
||||
} else {
|
||||
ek.type = "bip32";
|
||||
}
|
||||
ew.use_encryption = false;
|
||||
} else if(keystore.getSource() == KeystoreSource.SW_SEED) {
|
||||
ek.type = "bip32";
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GordianSeedTool implements KeystoreFileImport {
|
||||
|
||||
@Override
|
||||
public String getKeystoreImportDescription(int account) {
|
||||
return "Select your seed and scan the QR code created by Authenticate > Derive Key > Other Key Derivations > " + Network.get().toDisplayString() + " > Master Key > Account Descriptor. Click the share icon at the bottom.";
|
||||
return "Select your seed and scan the QR code created by Authenticate > Derive Key > Other Key Derivations > " + Network.getCanonical().getCapitalizedName() + " > Master Key > Account Descriptor. Click the share icon at the bottom.";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -601,7 +601,7 @@ public class Hwi {
|
||||
private void addChainType(List<String> elements, boolean commandPresent) {
|
||||
if(Network.get() != Network.MAINNET) {
|
||||
elements.add(elements.size() - (commandPresent ? 1 : 0), "--chain");
|
||||
elements.add(elements.size() - (commandPresent ? 1 : 0), getChainName(Network.get()));
|
||||
elements.add(elements.size() - (commandPresent ? 1 : 0), getChainName(Network.getCanonical()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,9 @@ import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||
import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.hummingbird.UREncoder;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQR;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQREncoder;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQREncoding;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Stage;
|
||||
@@ -39,7 +42,7 @@ public class PdfUtils {
|
||||
private static final int QR_WIDTH = 480;
|
||||
private static final int QR_HEIGHT = 480;
|
||||
|
||||
public static void saveOutputDescriptor(String walletName, String outputDescriptor, UR ur) {
|
||||
public static void saveOutputDescriptor(String walletName, String outputDescriptor, UR ur, BBQR bbqr) {
|
||||
Stage window = new Stage();
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Save PDF");
|
||||
@@ -56,9 +59,22 @@ public class PdfUtils {
|
||||
Chunk title = new Chunk("Output descriptor for " + walletName, titleFont);
|
||||
document.add(title);
|
||||
|
||||
UREncoder urEncoder = new UREncoder(ur, 2000, 10, 0);
|
||||
String fragment = urEncoder.nextPart();
|
||||
if(urEncoder.isSinglePart()) {
|
||||
String fragment = null;
|
||||
if(bbqr != null) {
|
||||
BBQREncoder bbqrEncoder = new BBQREncoder(bbqr.type(), BBQREncoding.ZLIB, bbqr.data(), 2000, 0);
|
||||
if(bbqrEncoder.isSinglePart()) {
|
||||
fragment = bbqrEncoder.nextPart();
|
||||
}
|
||||
}
|
||||
|
||||
if(fragment == null) {
|
||||
UREncoder urEncoder = new UREncoder(ur, 2000, 10, 0);
|
||||
if(urEncoder.isSinglePart()) {
|
||||
fragment = urEncoder.nextPart();
|
||||
}
|
||||
}
|
||||
|
||||
if(fragment != null) {
|
||||
Image image = Image.getInstance(SwingFXUtils.fromFXImage(getQrCode(fragment), null), Color.WHITE);
|
||||
document.add(image);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.security.cert.Certificate;
|
||||
@@ -216,8 +217,8 @@ public class Storage {
|
||||
}
|
||||
|
||||
private void checkWalletNetwork(Wallet wallet) {
|
||||
if(wallet.getNetwork() != null && wallet.getNetwork() != Network.get()) {
|
||||
throw new IllegalStateException("Provided " + wallet.getNetwork() + " wallet is invalid on a " + Network.get() + " network. Use a " + wallet.getNetwork() + " configuration to load this wallet.");
|
||||
if(wallet.getNetwork() != null && wallet.getNetwork() != Network.getCanonical()) {
|
||||
throw new IllegalStateException("Provided " + wallet.getNetwork() + " wallet is invalid on a " + Network.getCanonical() + " network. Use a " + wallet.getNetwork() + " configuration to load this wallet.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,8 +527,21 @@ public class Storage {
|
||||
|
||||
public static File getSparrowDir() {
|
||||
File sparrowDir;
|
||||
if(Network.get() != Network.MAINNET) {
|
||||
sparrowDir = new File(getSparrowHome(), Network.get().getName());
|
||||
Network network = Network.get();
|
||||
if(network != Network.MAINNET) {
|
||||
sparrowDir = new File(getSparrowHome(), network.getHome());
|
||||
if(!network.getName().equals(network.getHome()) && !sparrowDir.exists()) {
|
||||
File networkNameDir = new File(getSparrowHome(), network.getName());
|
||||
if(networkNameDir.exists() && networkNameDir.isDirectory() && !Files.isSymbolicLink(networkNameDir.toPath())) {
|
||||
try {
|
||||
if(networkNameDir.renameTo(sparrowDir) && !isWindows()) {
|
||||
Files.createSymbolicLink(networkNameDir.toPath(), Path.of(sparrowDir.getName()));
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.debug("Error creating symlink from " + networkNameDir.getAbsolutePath() + " to " + sparrowDir.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sparrowDir = getSparrowHome();
|
||||
}
|
||||
@@ -536,6 +550,23 @@ public class Storage {
|
||||
createOwnerOnlyDirectory(sparrowDir);
|
||||
}
|
||||
|
||||
if(!network.getName().equals(network.getHome()) && !isWindows()) {
|
||||
try {
|
||||
Path networkNamePath = getSparrowHome().toPath().resolve(network.getName());
|
||||
if(Files.isSymbolicLink(networkNamePath)) {
|
||||
Path symlinkTarget = getSparrowHome().toPath().resolve(Files.readSymbolicLink(networkNamePath));
|
||||
if(!Files.isSameFile(sparrowDir.toPath(), symlinkTarget)) {
|
||||
Files.delete(networkNamePath);
|
||||
Files.createSymbolicLink(networkNamePath, Path.of(sparrowDir.getName()));
|
||||
}
|
||||
} else if(!Files.exists(networkNamePath)) {
|
||||
Files.createSymbolicLink(networkNamePath, Path.of(sparrowDir.getName()));
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.debug("Error updating symlink from " + network.getName() + " to " + sparrowDir.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return sparrowDir;
|
||||
}
|
||||
|
||||
@@ -548,7 +579,7 @@ public class Storage {
|
||||
return new File(System.getProperty(SparrowWallet.APP_HOME_PROPERTY));
|
||||
}
|
||||
|
||||
if(Platform.getCurrent() == Platform.WINDOWS) {
|
||||
if(isWindows()) {
|
||||
return new File(getHomeDir(), WINDOWS_SPARROW_DIR);
|
||||
}
|
||||
|
||||
@@ -556,7 +587,7 @@ public class Storage {
|
||||
}
|
||||
|
||||
static File getHomeDir() {
|
||||
if(Platform.getCurrent() == Platform.WINDOWS) {
|
||||
if(isWindows()) {
|
||||
return new File(System.getenv("APPDATA"));
|
||||
}
|
||||
|
||||
@@ -565,7 +596,7 @@ public class Storage {
|
||||
|
||||
public static boolean createOwnerOnlyDirectory(File directory) {
|
||||
try {
|
||||
if(Platform.getCurrent() == Platform.WINDOWS) {
|
||||
if(isWindows()) {
|
||||
Files.createDirectories(directory.toPath());
|
||||
return true;
|
||||
}
|
||||
@@ -583,7 +614,7 @@ public class Storage {
|
||||
|
||||
public static boolean createOwnerOnlyFile(File file) {
|
||||
try {
|
||||
if(Platform.getCurrent() == Platform.WINDOWS) {
|
||||
if(isWindows()) {
|
||||
Files.createFile(file.toPath());
|
||||
return true;
|
||||
}
|
||||
@@ -614,6 +645,10 @@ public class Storage {
|
||||
return ownerOnly;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
return Platform.getCurrent() == Platform.WINDOWS;
|
||||
}
|
||||
|
||||
public static class LoadWalletService extends Service<WalletAndKey> {
|
||||
private final Storage storage;
|
||||
private final SecureString password;
|
||||
|
||||
@@ -37,6 +37,7 @@ public class BBQRDecoder {
|
||||
|
||||
if(receivedParts.size() == totalParts) {
|
||||
byte[] data = concatParts();
|
||||
data = header.inflate(data);
|
||||
|
||||
if(type == BBQRType.PSBT) {
|
||||
result = new Result(new PSBT(data));
|
||||
|
||||
@@ -35,7 +35,7 @@ public class BBQREncoder {
|
||||
BBQREncoding encoding = desiredEncoding;
|
||||
|
||||
try {
|
||||
encoded = encoding.encode(data);
|
||||
encoded = encoding.encode(encoding.deflate(data));
|
||||
if(encoding == BBQREncoding.ZLIB) {
|
||||
String uncompressed = BBQREncoding.BASE32.encode(data);
|
||||
if(encoded.length() > uncompressed.length()) {
|
||||
|
||||
@@ -42,6 +42,11 @@ public enum BBQREncoding {
|
||||
}, ZLIB("Z") {
|
||||
@Override
|
||||
public String encode(byte[] data) throws BBQREncodingException {
|
||||
return BASE32.encode(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] deflate(byte[] data) throws BBQREncodingException {
|
||||
try {
|
||||
Deflater deflater = new Deflater(JZlib.Z_BEST_COMPRESSION, 10, true);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
@@ -49,7 +54,7 @@ public enum BBQREncoding {
|
||||
zOut.write(data);
|
||||
zOut.close();
|
||||
|
||||
return BASE32.encode(out.toByteArray());
|
||||
return out.toByteArray();
|
||||
} catch(Exception e) {
|
||||
throw new BBQREncodingException("Error deflating with zlib", e);
|
||||
}
|
||||
@@ -57,9 +62,14 @@ public enum BBQREncoding {
|
||||
|
||||
@Override
|
||||
public byte[] decode(String part) throws BBQREncodingException {
|
||||
return BASE32.decode(part);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] inflate(byte[] data) throws BBQREncodingException {
|
||||
try {
|
||||
Inflater inflater = new Inflater(10, true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(BASE32.decode(part));
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
InflaterInputStream zIn = new InflaterInputStream(in, inflater);
|
||||
byte[] decoded = zIn.readAllBytes();
|
||||
zIn.close();
|
||||
@@ -96,6 +106,14 @@ public enum BBQREncoding {
|
||||
return code;
|
||||
}
|
||||
|
||||
public byte[] deflate(byte[] data) throws BBQREncodingException {
|
||||
return data;
|
||||
}
|
||||
|
||||
public byte[] inflate(byte[] data) throws BBQREncodingException {
|
||||
return data;
|
||||
}
|
||||
|
||||
public abstract String encode(byte[] data) throws BBQREncodingException;
|
||||
|
||||
public abstract byte[] decode(String part) throws BBQREncodingException;
|
||||
|
||||
@@ -13,6 +13,10 @@ public record BBQRHeader(BBQREncoding encoding, BBQRType type, int seqTotal, int
|
||||
return encoding.decode(part.substring(8));
|
||||
}
|
||||
|
||||
public byte[] inflate(byte[] data) {
|
||||
return encoding.inflate(data);
|
||||
}
|
||||
|
||||
public static BBQRHeader fromString(String part) {
|
||||
if(part.length() < 8) {
|
||||
throw new IllegalArgumentException("Part too short");
|
||||
|
||||
@@ -46,7 +46,7 @@ public enum BroadcastSource {
|
||||
|
||||
@Override
|
||||
public List<Network> getSupportedNetworks() {
|
||||
return List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET);
|
||||
return List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET, Network.TESTNET4);
|
||||
}
|
||||
|
||||
protected URL getURL(HostAndPort proxy) throws MalformedURLException {
|
||||
@@ -56,6 +56,8 @@ public enum BroadcastSource {
|
||||
return new URL(getBaseUrl(proxy) + "/testnet/api/tx");
|
||||
} else if(Network.get() == Network.SIGNET) {
|
||||
return new URL(getBaseUrl(proxy) + "/signet/api/tx");
|
||||
} else if(Network.get() == Network.TESTNET4) {
|
||||
return new URL(getBaseUrl(proxy) + "/testnet4/api/tx");
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot broadcast transaction to " + getName() + " on network " + Network.get());
|
||||
}
|
||||
@@ -77,6 +79,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());
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ public class Bwt {
|
||||
*/
|
||||
private void start(Collection<String> outputDescriptors, Collection<String> addresses, Integer rescanSince, Boolean forceRescan, Integer gapLimit, CallbackNotifier callback) {
|
||||
BwtConfig bwtConfig = new BwtConfig();
|
||||
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.get().getName();
|
||||
bwtConfig.network = Network.get() == Network.MAINNET ? "bitcoin" : Network.getCanonical().getName();
|
||||
|
||||
if(!outputDescriptors.isEmpty()) {
|
||||
bwtConfig.descriptors = outputDescriptors;
|
||||
|
||||
@@ -17,14 +17,15 @@ public enum PublicElectrumServer {
|
||||
SETHFORPRIVACY_COM("fulcrum.sethforprivacy.com", "ssl://fulcrum.sethforprivacy.com:50002", Network.MAINNET),
|
||||
TESTNET_ARANGUREN_ORG("testnet.aranguren.org", "ssl://testnet.aranguren.org:51002", Network.TESTNET),
|
||||
TESTNET_QTORNADO_COM("testnet.qtornado.com", "ssl://testnet.qtornado.com:51002", Network.TESTNET),
|
||||
SIGNET_MEMPOOL_SPACE("mempool.space", "ssl://mempool.space:60602", Network.SIGNET);
|
||||
SIGNET_MEMPOOL_SPACE("mempool.space", "ssl://mempool.space:60602", Network.SIGNET),
|
||||
TESTNET4_MEMPOOL_SPACE("mempool.space", "ssl://mempool.space:40002", Network.TESTNET4);
|
||||
|
||||
PublicElectrumServer(String name, String url, Network network) {
|
||||
this.server = new Server(url, name);
|
||||
this.network = network;
|
||||
}
|
||||
|
||||
public static final List<Network> SUPPORTED_NETWORKS = List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET);
|
||||
public static final List<Network> SUPPORTED_NETWORKS = List.of(Network.MAINNET, Network.TESTNET, Network.SIGNET, Network.TESTNET4);
|
||||
|
||||
private final Server server;
|
||||
private final Network network;
|
||||
|
||||
@@ -130,6 +130,8 @@ public class BitcoindTransport implements Transport {
|
||||
private static File getCookieDir(File bitcoindDir) {
|
||||
if(Network.get() == Network.TESTNET && Files.exists(Path.of(bitcoindDir.getAbsolutePath(), "testnet3", COOKIE_FILENAME))) {
|
||||
return new File(bitcoindDir, "testnet3");
|
||||
} else if(Network.get() == Network.TESTNET4 && Files.exists(Path.of(bitcoindDir.getAbsolutePath(), "testnet4", COOKIE_FILENAME))) {
|
||||
return new File(bitcoindDir, "testnet4");
|
||||
} else if(Network.get() == Network.REGTEST && Files.exists(Path.of(bitcoindDir.getAbsolutePath(), "regtest", COOKIE_FILENAME))) {
|
||||
return new File(bitcoindDir, "regtest");
|
||||
} else if(Network.get() == Network.SIGNET && Files.exists(Path.of(bitcoindDir.getAbsolutePath(), "signet", COOKIE_FILENAME))) {
|
||||
|
||||
@@ -905,9 +905,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
toggleButton.setSelected(false);
|
||||
|
||||
//TODO: Remove once Cobo Vault support has been removed
|
||||
boolean addLegacyEncodingOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().equals(WalletModel.COBO_VAULT));
|
||||
boolean addBbqrOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().equals(WalletModel.COLDCARD) || keystore.getSource().equals(KeystoreSource.SW_WATCH) || keystore.getSource().equals(KeystoreSource.SW_SEED));
|
||||
boolean selectBbqrOption = headersForm.getSigningWallet().getKeystores().stream().allMatch(keystore -> keystore.getWalletModel().equals(WalletModel.COLDCARD));
|
||||
boolean addLegacyEncodingOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().showLegacyQR());
|
||||
boolean addBbqrOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().showBbqr());
|
||||
boolean selectBbqrOption = headersForm.getSigningWallet().getKeystores().stream().allMatch(keystore -> keystore.getWalletModel().selectBbqr());
|
||||
|
||||
//Don't include non witness utxo fields for segwit wallets when displaying the PSBT as a QR - it can add greatly to the time required for scanning
|
||||
boolean includeNonWitnessUtxos = !Arrays.asList(ScriptType.WITNESS_TYPES).contains(headersForm.getSigningWallet().getScriptType());
|
||||
@@ -1386,8 +1386,9 @@ public class HeadersController extends TransactionFormController implements Init
|
||||
@Subscribe
|
||||
public void openWallets(OpenWalletsEvent event) {
|
||||
if(id.getScene().getWindow().equals(event.getWindow()) && headersForm.getPsbt() != null && headersForm.getBlockTransaction() == null) {
|
||||
List<Wallet> availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSignAllInputs(headersForm.getPsbt())).sorted(new WalletSignComparator()).collect(Collectors.toList());
|
||||
if(availableWallets.isEmpty()) {
|
||||
List<Wallet> availableWallets = event.getWallets().stream().filter(wallet -> wallet.canSign(headersForm.getPsbt())).sorted(new WalletSignComparator()).collect(Collectors.toList());
|
||||
List<Wallet> signingAllInputsWallets = event.getWallets().stream().filter(wallet -> wallet.canSignAllInputs(headersForm.getPsbt())).sorted(new WalletSignComparator()).collect(Collectors.toList());
|
||||
if(availableWallets.isEmpty() || !availableWallets.equals(signingAllInputsWallets)) {
|
||||
for(Wallet wallet : event.getWalletsMap().keySet()) {
|
||||
if(wallet.isValid() && !wallet.getSigningKeystores(headersForm.getPsbt()).isEmpty()) {
|
||||
int currentGapLimit = wallet.getGapLimit();
|
||||
|
||||
@@ -5,12 +5,16 @@ import com.sparrowwallet.hummingbird.UR;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.PdfUtils;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQR;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQRType;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class MultisigBackupDialog extends Dialog<String> {
|
||||
private final Wallet wallet;
|
||||
private final String descriptor;
|
||||
@@ -71,7 +75,12 @@ public class MultisigBackupDialog extends Dialog<String> {
|
||||
final ButtonBar.ButtonData buttonData = buttonType.getButtonData();
|
||||
ButtonBar.setButtonData(pdfButton, buttonData);
|
||||
pdfButton.setOnAction(event -> {
|
||||
PdfUtils.saveOutputDescriptor(wallet.getFullDisplayName(), descriptor, ur);
|
||||
BBQR bbqr = null;
|
||||
if(wallet.getKeystores().stream().allMatch(keystore -> keystore.getWalletModel().selectBbqr())) {
|
||||
bbqr = new BBQR(BBQRType.UNICODE, descriptor.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
PdfUtils.saveOutputDescriptor(wallet.getFullDisplayName(), descriptor, ur, bbqr);
|
||||
});
|
||||
|
||||
button = pdfButton;
|
||||
|
||||
@@ -18,6 +18,8 @@ import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.io.StorageException;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQR;
|
||||
import com.sparrowwallet.sparrow.io.bbqr.BBQRType;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
import com.sparrowwallet.sparrow.net.ServerType;
|
||||
import javafx.application.Platform;
|
||||
@@ -37,6 +39,7 @@ import tornadofx.control.Fieldset;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -376,8 +379,12 @@ public class SettingsController extends WalletFormController implements Initiali
|
||||
return;
|
||||
}
|
||||
|
||||
boolean addBbqrOption = walletForm.getWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().showBbqr());
|
||||
boolean selectBbqrOption = walletForm.getWallet().getKeystores().stream().allMatch(keystore -> keystore.getWalletModel().selectBbqr());
|
||||
|
||||
UR cryptoOutputUR = cryptoOutput.toUR();
|
||||
QRDisplayDialog qrDisplayDialog = new DescriptorQRDisplayDialog(walletForm.getWallet().getFullDisplayName(), outputDescriptor.toString(true), cryptoOutputUR);
|
||||
BBQR bbqr = addBbqrOption ? new BBQR(BBQRType.UNICODE, outputDescriptor.toString(true).getBytes(StandardCharsets.UTF_8)) : null;
|
||||
QRDisplayDialog qrDisplayDialog = new DescriptorQRDisplayDialog(walletForm.getWallet().getFullDisplayName(), outputDescriptor.toString(true), cryptoOutputUR, bbqr, selectBbqrOption);
|
||||
qrDisplayDialog.initOwner(showDescriptorQR.getScene().getWindow());
|
||||
qrDisplayDialog.showAndWait();
|
||||
}
|
||||
|
||||
@@ -122,8 +122,8 @@ HorizontalHeaderColumn > TableColumnHeader.column-header.table-column{
|
||||
-fx-bar-fill: rgba(135, 138, 149, 0.5);
|
||||
}
|
||||
|
||||
.tab-label .failure {
|
||||
-fx-fill: #e06c75;
|
||||
.root .tab-pane > .tab-header-area > .headers-region > .tab .tab-label .glyph-font.failure {
|
||||
-fx-text-fill: #e06c75;
|
||||
}
|
||||
|
||||
.status-bar .tor-status.failure .glyph-font {
|
||||
|
||||
Reference in New Issue
Block a user