mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-11-05 05:46:44 +00:00
add ability to import keystores and wallets with QR, add part protocol to QR scanner
This commit is contained in:
parent
20f5e4d8db
commit
76e148a107
14 changed files with 179 additions and 42 deletions
|
@ -559,17 +559,14 @@ public class AppController implements Initializable {
|
||||||
if(result.transaction != null) {
|
if(result.transaction != null) {
|
||||||
Tab tab = addTransactionTab(null, result.transaction);
|
Tab tab = addTransactionTab(null, result.transaction);
|
||||||
tabs.getSelectionModel().select(tab);
|
tabs.getSelectionModel().select(tab);
|
||||||
}
|
} else if(result.psbt != null) {
|
||||||
if(result.psbt != null) {
|
|
||||||
Tab tab = addTransactionTab(null, result.psbt);
|
Tab tab = addTransactionTab(null, result.psbt);
|
||||||
tabs.getSelectionModel().select(tab);
|
tabs.getSelectionModel().select(tab);
|
||||||
}
|
} else if(result.exception != null) {
|
||||||
if(result.error != null) {
|
|
||||||
showErrorDialog("Invalid QR Code", result.error);
|
|
||||||
}
|
|
||||||
if(result.exception != null) {
|
|
||||||
log.error("Error opening webcam", result.exception);
|
log.error("Error opening webcam", result.exception);
|
||||||
showErrorDialog("Error opening webcam", result.exception.getMessage());
|
showErrorDialog("Error opening webcam", result.exception.getMessage());
|
||||||
|
} else {
|
||||||
|
AppController.showErrorDialog("Invalid QR Code", "Cannot parse QR code into a transaction or PSBT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
import com.google.gson.JsonParseException;
|
||||||
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
import com.sparrowwallet.drongo.crypto.InvalidPasswordException;
|
||||||
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.FileImport;
|
import com.sparrowwallet.sparrow.io.FileImport;
|
||||||
import com.sparrowwallet.sparrow.io.ImportException;
|
import com.sparrowwallet.sparrow.io.ImportException;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -9,41 +10,72 @@ import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.ButtonBase;
|
||||||
import javafx.scene.control.Control;
|
import javafx.scene.control.Control;
|
||||||
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
import javafx.scene.layout.Priority;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.controlsfx.control.SegmentedButton;
|
||||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||||
import org.controlsfx.control.textfield.TextFields;
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.*;
|
||||||
import java.io.File;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.io.FileInputStream;
|
import java.util.Optional;
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public abstract class FileImportPane extends TitledDescriptionPane {
|
public abstract class FileImportPane extends TitledDescriptionPane {
|
||||||
private static final Logger log = LoggerFactory.getLogger(FileImportPane.class);
|
private static final Logger log = LoggerFactory.getLogger(FileImportPane.class);
|
||||||
|
|
||||||
private final FileImport importer;
|
private final FileImport importer;
|
||||||
protected Button importButton;
|
protected ButtonBase importButton;
|
||||||
private final SimpleStringProperty password = new SimpleStringProperty("");
|
private final SimpleStringProperty password = new SimpleStringProperty("");
|
||||||
|
private final boolean scannable;
|
||||||
|
|
||||||
public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl) {
|
public FileImportPane(FileImport importer, String title, String description, String content, String imageUrl, boolean scannable) {
|
||||||
super(title, description, content, imageUrl);
|
super(title, description, content, imageUrl);
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
|
this.scannable = scannable;
|
||||||
|
|
||||||
|
buttonBox.getChildren().clear();
|
||||||
|
buttonBox.getChildren().add(createButton());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Control createButton() {
|
protected Control createButton() {
|
||||||
importButton = new Button("Import File...");
|
if(scannable) {
|
||||||
importButton.setAlignment(Pos.CENTER_RIGHT);
|
ToggleButton scanButton = new ToggleButton("Scan...");
|
||||||
importButton.setOnAction(event -> {
|
Glyph cameraGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.CAMERA);
|
||||||
importFile();
|
cameraGlyph.setFontSize(12);
|
||||||
});
|
scanButton.setGraphic(cameraGlyph);
|
||||||
return importButton;
|
scanButton.setOnAction(event -> {
|
||||||
|
scanButton.setSelected(false);
|
||||||
|
importQR();
|
||||||
|
});
|
||||||
|
|
||||||
|
ToggleButton fileButton = new ToggleButton("Import File...");
|
||||||
|
fileButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
fileButton.setOnAction(event -> {
|
||||||
|
fileButton.setSelected(false);
|
||||||
|
importFile();
|
||||||
|
});
|
||||||
|
importButton = fileButton;
|
||||||
|
|
||||||
|
SegmentedButton segmentedButton = new SegmentedButton();
|
||||||
|
segmentedButton.getButtons().addAll(scanButton, fileButton);
|
||||||
|
return segmentedButton;
|
||||||
|
} else {
|
||||||
|
importButton = new Button("Import File...");
|
||||||
|
importButton.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
importButton.setOnAction(event -> {
|
||||||
|
importFile();
|
||||||
|
});
|
||||||
|
return importButton;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importFile() {
|
private void importFile() {
|
||||||
|
@ -94,6 +126,29 @@ public abstract class FileImportPane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void importQR() {
|
||||||
|
QRScanDialog qrScanDialog = new QRScanDialog();
|
||||||
|
Optional<QRScanDialog.Result> optionalResult = qrScanDialog.showAndWait();
|
||||||
|
if(optionalResult.isPresent()) {
|
||||||
|
QRScanDialog.Result result = optionalResult.get();
|
||||||
|
if(result.payload != null) {
|
||||||
|
try {
|
||||||
|
importFile(importer.getName(), new ByteArrayInputStream(result.payload.getBytes(StandardCharsets.UTF_8)), null);
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error("Error importing QR", e);
|
||||||
|
String errorMessage = e.getMessage();
|
||||||
|
if(e.getCause() != null && e.getCause().getMessage() != null && !e.getCause().getMessage().isEmpty()) {
|
||||||
|
errorMessage = e.getCause().getMessage();
|
||||||
|
}
|
||||||
|
if(e instanceof JsonParseException || e.getCause() instanceof JsonParseException) {
|
||||||
|
errorMessage = "QR contents were not in JSON format";
|
||||||
|
}
|
||||||
|
setError("Import Error", errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void importFile(String fileName, InputStream inputStream, String password) throws ImportException;
|
protected abstract void importFile(String fileName, InputStream inputStream, String password) throws ImportException;
|
||||||
|
|
||||||
private Node getPasswordEntry(File file) {
|
private Node getPasswordEntry(File file) {
|
||||||
|
|
|
@ -14,7 +14,7 @@ public class FileKeystoreImportPane extends FileImportPane {
|
||||||
private final KeystoreFileImport importer;
|
private final KeystoreFileImport importer;
|
||||||
|
|
||||||
public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer) {
|
public FileKeystoreImportPane(Wallet wallet, KeystoreFileImport importer) {
|
||||||
super(importer, importer.getName(), "Keystore file import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
super(importer, importer.getName(), "Keystore import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isScannable());
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ public class FileWalletImportPane extends FileImportPane {
|
||||||
private final WalletImport importer;
|
private final WalletImport importer;
|
||||||
|
|
||||||
public FileWalletImportPane(WalletImport importer) {
|
public FileWalletImportPane(WalletImport importer) {
|
||||||
super(importer, importer.getName(), "Wallet file import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
super(importer, importer.getName(), "Wallet import", importer.getWalletImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isScannable());
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane {
|
||||||
private byte[] fileBytes;
|
private byte[] fileBytes;
|
||||||
|
|
||||||
public FileWalletKeystoreImportPane(KeystoreFileImport importer) {
|
public FileWalletKeystoreImportPane(KeystoreFileImport importer) {
|
||||||
super(importer, importer.getName(), "Wallet file import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png");
|
super(importer, importer.getName(), "Wallet import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isScannable());
|
||||||
this.importer = importer;
|
this.importer = importer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,23 @@ import javafx.scene.control.DialogPane;
|
||||||
import javafx.scene.layout.StackPane;
|
import javafx.scene.layout.StackPane;
|
||||||
import org.controlsfx.tools.Borders;
|
import org.controlsfx.tools.Borders;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
private final URDecoder decoder;
|
private final URDecoder decoder;
|
||||||
private final WebcamService webcamService;
|
private final WebcamService webcamService;
|
||||||
|
private List<String> parts;
|
||||||
|
|
||||||
private boolean isUr;
|
private boolean isUr;
|
||||||
private QRScanDialog.Result result;
|
private QRScanDialog.Result result;
|
||||||
|
|
||||||
|
private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)");
|
||||||
|
|
||||||
public QRScanDialog() {
|
public QRScanDialog() {
|
||||||
this.decoder = new URDecoder();
|
this.decoder = new URDecoder();
|
||||||
|
|
||||||
|
@ -69,6 +79,8 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
|
|
||||||
//Try text first
|
//Try text first
|
||||||
String qrtext = qrResult.getText();
|
String qrtext = qrResult.getText();
|
||||||
|
Matcher partMatcher = PART_PATTERN.matcher(qrtext);
|
||||||
|
|
||||||
if(isUr || qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) {
|
if(isUr || qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) {
|
||||||
isUr = true;
|
isUr = true;
|
||||||
decoder.receivePart(qrtext);
|
decoder.receivePart(qrtext);
|
||||||
|
@ -102,6 +114,37 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
result = new Result(urResult.error);
|
result = new Result(urResult.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if(partMatcher.matches()) {
|
||||||
|
int m = Integer.parseInt(partMatcher.group(1));
|
||||||
|
int n = Integer.parseInt(partMatcher.group(2));
|
||||||
|
String payload = partMatcher.group(3);
|
||||||
|
|
||||||
|
if(parts == null) {
|
||||||
|
parts = new ArrayList<>(n);
|
||||||
|
IntStream.range(0, n).forEach(i -> parts.add(null));
|
||||||
|
}
|
||||||
|
parts.set(m - 1, payload);
|
||||||
|
|
||||||
|
if(parts.stream().filter(Objects::nonNull).count() == n) {
|
||||||
|
String complete = String.join("", parts);
|
||||||
|
try {
|
||||||
|
PSBT psbt = PSBT.fromString(complete);
|
||||||
|
result = new Result(psbt);
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//ignore, bytes not parsable as PSBT
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Transaction transaction = new Transaction(Utils.hexToBytes(complete));
|
||||||
|
result = new Result(transaction);
|
||||||
|
return;
|
||||||
|
} catch(Exception e) {
|
||||||
|
//ignore, bytes not parsable as tx
|
||||||
|
}
|
||||||
|
|
||||||
|
result = new Result("Parsed QR parts were not a PSBT or transaction");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
PSBT psbt;
|
PSBT psbt;
|
||||||
Transaction transaction;
|
Transaction transaction;
|
||||||
|
@ -166,7 +209,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
|
|
||||||
//Try Base43 used by Electrum
|
//Try Base43 used by Electrum
|
||||||
try {
|
try {
|
||||||
psbt = new PSBT(Base43.decode(qrResult.getText()));
|
psbt = new PSBT(Base43.decode(qrtext));
|
||||||
result = new Result(psbt);
|
result = new Result(psbt);
|
||||||
return;
|
return;
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
|
@ -174,14 +217,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
transaction = new Transaction(Base43.decode(qrResult.getText()));
|
transaction = new Transaction(Base43.decode(qrtext));
|
||||||
result = new Result(transaction);
|
result = new Result(transaction);
|
||||||
return;
|
return;
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
//Ignore, not parseable as base43 decoded bytes
|
//Ignore, not parseable as base43 decoded bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
result = new Result("Cannot parse QR code into a PSBT, transaction or address");
|
result = new Result(qrtext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -191,7 +234,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
public final PSBT psbt;
|
public final PSBT psbt;
|
||||||
public final BitcoinURI uri;
|
public final BitcoinURI uri;
|
||||||
public final ExtendedKey extendedKey;
|
public final ExtendedKey extendedKey;
|
||||||
public final String error;
|
public final String payload;
|
||||||
public final Throwable exception;
|
public final Throwable exception;
|
||||||
|
|
||||||
public Result(Transaction transaction) {
|
public Result(Transaction transaction) {
|
||||||
|
@ -199,7 +242,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +251,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = psbt;
|
this.psbt = psbt;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +260,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = uri;
|
this.uri = uri;
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +269,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = BitcoinURI.fromAddress(address);
|
this.uri = BitcoinURI.fromAddress(address);
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,16 +278,16 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.extendedKey = extendedKey;
|
this.extendedKey = extendedKey;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result(String error) {
|
public Result(String payload) {
|
||||||
this.transaction = null;
|
this.transaction = null;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = error;
|
this.payload = payload;
|
||||||
this.exception = null;
|
this.exception = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +296,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.uri = null;
|
this.uri = null;
|
||||||
this.extendedKey = null;
|
this.extendedKey = null;
|
||||||
this.error = null;
|
this.payload = null;
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class CoboVaultMultisig extends ColdcardMultisig {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKeystoreImportDescription() {
|
public String getKeystoreImportDescription() {
|
||||||
return "Import file created by using the Multisig Wallet > Show/Export XPUB > Export All > Export feature on your Cobo Vault.";
|
return "Import file or QR created by using the Multisig Wallet > Show/Export XPUB > Export All > Export feature on your Cobo Vault.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -45,11 +45,16 @@ public class CoboVaultMultisig extends ColdcardMultisig {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletImportDescription() {
|
public String getWalletImportDescription() {
|
||||||
return "Import file created by using the Multisig Wallet > Create Multisig Wallet feature on your Cobo Vault.";
|
return "Import file or QR created by using the Multisig Wallet > Create Multisig Wallet feature on your Cobo Vault.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletExportDescription() {
|
public String getWalletExportDescription() {
|
||||||
return "Export file that can be read by your Cobo Vault using the Multisig Wallet > Import Multisig Wallet feature.";
|
return "Export file that can be read by your Cobo Vault using the Multisig Wallet > Import Multisig Wallet feature.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getKeystoreImportDescription() {
|
public String getKeystoreImportDescription() {
|
||||||
return "Import file created by using the Watch-Only Wallet > Generic Wallet > Export Wallet feature on your Cobo Vault.";
|
return "Import file or QR created by using the Watch-Only Wallet > Generic Wallet > Export Wallet feature on your Cobo Vault.";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -35,7 +35,11 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
|
||||||
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
|
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
|
||||||
try {
|
try {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
CoboVaultKeystore coboKeystore = gson.fromJson(new InputStreamReader(inputStream), CoboVaultKeystore.class);
|
CoboVaultSinglesigKeystore coboKeystore = gson.fromJson(new InputStreamReader(inputStream), CoboVaultSinglesigKeystore.class);
|
||||||
|
|
||||||
|
if(coboKeystore.MasterFingerprint == null || coboKeystore.AccountKeyPath == null || coboKeystore.ExtPubKey == null) {
|
||||||
|
throw new ImportException("Not a valid " + getName() + " keystore export");
|
||||||
|
}
|
||||||
|
|
||||||
Keystore keystore = new Keystore();
|
Keystore keystore = new Keystore();
|
||||||
keystore.setLabel(getName());
|
keystore.setLabel(getName());
|
||||||
|
@ -83,7 +87,12 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CoboVaultKeystore {
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CoboVaultSinglesigKeystore {
|
||||||
public String ExtPubKey;
|
public String ExtPubKey;
|
||||||
public String MasterFingerprint;
|
public String MasterFingerprint;
|
||||||
public String AccountKeyPath;
|
public String AccountKeyPath;
|
||||||
|
|
|
@ -37,7 +37,14 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||||
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
|
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
|
||||||
keystore.setWalletModel(WalletModel.COLDCARD);
|
keystore.setWalletModel(WalletModel.COLDCARD);
|
||||||
|
|
||||||
if(scriptType.equals(ScriptType.P2SH)) {
|
if(cck.xpub != null && cck.path != null) {
|
||||||
|
ExtendedKey.Header header = ExtendedKey.Header.fromExtendedKey(cck.xpub);
|
||||||
|
if(header.getDefaultScriptType() != scriptType) {
|
||||||
|
throw new ImportException("This wallet's script type (" + scriptType + ") does not match the " + getName() + " script type (" + header.getDefaultScriptType() + ")");
|
||||||
|
}
|
||||||
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.path));
|
||||||
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.xpub));
|
||||||
|
} else if(scriptType.equals(ScriptType.P2SH)) {
|
||||||
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv));
|
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv));
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2sh));
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2sh));
|
||||||
} else if(scriptType.equals(ScriptType.P2SH_P2WSH)) {
|
} else if(scriptType.equals(ScriptType.P2SH_P2WSH)) {
|
||||||
|
@ -60,6 +67,8 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||||
public String p2wsh_p2sh;
|
public String p2wsh_p2sh;
|
||||||
public String p2wsh_deriv;
|
public String p2wsh_deriv;
|
||||||
public String p2wsh;
|
public String p2wsh;
|
||||||
|
public String xpub;
|
||||||
|
public String path;
|
||||||
public String xfp;
|
public String xfp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,4 +209,9 @@ public class ColdcardMultisig implements WalletImport, KeystoreFileImport, Walle
|
||||||
public boolean isEncrypted(File file) {
|
public boolean isEncrypted(File file) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,11 @@ public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
|
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -317,6 +317,11 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
|
||||||
return (FileType.BINARY.equals(IOUtils.getFileType(file)));
|
return (FileType.BINARY.equals(IOUtils.getFileType(file)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWalletExportDescription() {
|
public String getWalletExportDescription() {
|
||||||
return "Export this wallet as an Electrum wallet file.";
|
return "Export this wallet as an Electrum wallet file.";
|
||||||
|
|
|
@ -4,4 +4,5 @@ import java.io.File;
|
||||||
|
|
||||||
public interface FileImport extends Import {
|
public interface FileImport extends Import {
|
||||||
boolean isEncrypted(File file);
|
boolean isEncrypted(File file);
|
||||||
|
boolean isScannable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,11 @@ public class Specter implements WalletImport, WalletExport {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScannable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "Specter";
|
return "Specter";
|
||||||
|
|
|
@ -362,8 +362,6 @@ public class KeystoreController extends WalletFormController implements Initiali
|
||||||
QRScanDialog.Result result = optionalResult.get();
|
QRScanDialog.Result result = optionalResult.get();
|
||||||
if(result.extendedKey != null && result.extendedKey.getKey().isPubKeyOnly()) {
|
if(result.extendedKey != null && result.extendedKey.getKey().isPubKeyOnly()) {
|
||||||
xpub.setText(result.extendedKey.getExtendedKey());
|
xpub.setText(result.extendedKey.getExtendedKey());
|
||||||
} else if(result.error != null) {
|
|
||||||
AppController.showErrorDialog("Invalid QR Code", result.error);
|
|
||||||
} else if(result.exception != null) {
|
} else if(result.exception != null) {
|
||||||
log.error("Error opening webcam", result.exception);
|
log.error("Error opening webcam", result.exception);
|
||||||
AppController.showErrorDialog("Error opening webcam", result.exception.getMessage());
|
AppController.showErrorDialog("Error opening webcam", result.exception.getMessage());
|
||||||
|
|
Loading…
Reference in a new issue