mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add card scan to hwi enumeration and refactor device pane
This commit is contained in:
parent
7a99c4a11a
commit
4fb8c5a61b
7 changed files with 173 additions and 72 deletions
|
@ -14,6 +14,7 @@ import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.io.ckcard.CardApi;
|
||||||
import com.sparrowwallet.sparrow.net.Auth47;
|
import com.sparrowwallet.sparrow.net.Auth47;
|
||||||
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
import com.sparrowwallet.drongo.protocol.BlockHeader;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
@ -1112,7 +1113,7 @@ public class AppServices {
|
||||||
Wallet wallet = walletTabData.getWallet();
|
Wallet wallet = walletTabData.getWallet();
|
||||||
Storage storage = walletTabData.getStorage();
|
Storage storage = walletTabData.getStorage();
|
||||||
|
|
||||||
if(Interface.get() == Interface.DESKTOP && (!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB))) {
|
if(Interface.get() == Interface.DESKTOP && (!storage.getWalletFile().exists() || wallet.containsSource(KeystoreSource.HW_USB) || CardApi.isReaderAvailable())) {
|
||||||
usbWallet = true;
|
usbWallet = true;
|
||||||
|
|
||||||
if(deviceEnumerateService == null) {
|
if(deviceEnumerateService == null) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.sparrowwallet.sparrow.event.KeystoreImportEvent;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.KeystoreCardImport;
|
import com.sparrowwallet.sparrow.io.KeystoreCardImport;
|
||||||
import com.sparrowwallet.sparrow.io.ckcard.CardAuthorizationException;
|
import com.sparrowwallet.sparrow.io.ckcard.CardAuthorizationException;
|
||||||
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
@ -153,6 +154,7 @@ public class CardImportPane extends TitledDescriptionPane {
|
||||||
importButton.setDefaultButton(true);
|
importButton.setDefaultButton(true);
|
||||||
pin.bind(pinField.textProperty());
|
pin.bind(pinField.textProperty());
|
||||||
HBox.setHgrow(pinField, Priority.ALWAYS);
|
HBox.setHgrow(pinField, Priority.ALWAYS);
|
||||||
|
Platform.runLater(pinField::requestFocus);
|
||||||
|
|
||||||
HBox contentBox = new HBox();
|
HBox contentBox = new HBox();
|
||||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||||
|
|
|
@ -8,20 +8,26 @@ import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
import com.sparrowwallet.drongo.policy.Policy;
|
import com.sparrowwallet.drongo.policy.Policy;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.policy.PolicyType;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
|
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.wallet.*;
|
import com.sparrowwallet.drongo.wallet.*;
|
||||||
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.*;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.io.Device;
|
import com.sparrowwallet.sparrow.io.Device;
|
||||||
import com.sparrowwallet.sparrow.io.Hwi;
|
import com.sparrowwallet.sparrow.io.Hwi;
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
|
import com.sparrowwallet.sparrow.io.KeystoreCardImport;
|
||||||
import com.sparrowwallet.sparrow.io.ckcard.CardApi;
|
import com.sparrowwallet.sparrow.io.ckcard.CardApi;
|
||||||
import com.sparrowwallet.sparrow.io.ckcard.CardAuthorizationException;
|
import com.sparrowwallet.sparrow.io.ckcard.CardAuthorizationException;
|
||||||
|
import com.sparrowwallet.sparrow.io.ckcard.CkCard;
|
||||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
@ -36,6 +42,7 @@ import org.controlsfx.validation.decoration.StyleClassValidationDecoration;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -562,7 +569,27 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importKeystore(List<ChildNumber> derivation) {
|
private void importKeystore(List<ChildNumber> derivation) {
|
||||||
if(device.getFingerprint() == null) {
|
if(device.isCard()) {
|
||||||
|
try {
|
||||||
|
CkCard importer = new CkCard();
|
||||||
|
if(!importer.isInitialized()) {
|
||||||
|
setDescription("Card not initialized");
|
||||||
|
setContent(getCardInitializationPanel(importer));
|
||||||
|
showHideLink.setVisible(false);
|
||||||
|
setExpanded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Service<Keystore> importService = new CardImportPane.CardImportService(importer, pin.get(), derivation);
|
||||||
|
handleCardOperation(importService, importButton, "Import", event -> {
|
||||||
|
importKeystore(derivation, importService.getValue());
|
||||||
|
});
|
||||||
|
} catch(Exception e) {
|
||||||
|
log.error("Import Error: " + e.getMessage(), e);
|
||||||
|
setError("Import Error", e.getMessage());
|
||||||
|
importButton.setDisable(false);
|
||||||
|
}
|
||||||
|
} else if(device.getFingerprint() == null) {
|
||||||
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(passphrase.get());
|
Hwi.EnumerateService enumerateService = new Hwi.EnumerateService(passphrase.get());
|
||||||
enumerateService.setOnSucceeded(workerStateEvent -> {
|
enumerateService.setOnSucceeded(workerStateEvent -> {
|
||||||
List<Device> devices = enumerateService.getValue();
|
List<Device> devices = enumerateService.getValue();
|
||||||
|
@ -599,18 +626,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
keystore.setKeyDerivation(new KeyDerivation(device.getFingerprint(), derivationPath));
|
keystore.setKeyDerivation(new KeyDerivation(device.getFingerprint(), derivationPath));
|
||||||
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(xpub));
|
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(xpub));
|
||||||
|
|
||||||
if(wallet.getScriptType() == null) {
|
importKeystore(derivation, keystore);
|
||||||
ScriptType scriptType = Arrays.stream(ScriptType.ADDRESSABLE_TYPES).filter(type -> type.getDefaultDerivation().get(0).equals(derivation.get(0))).findFirst().orElse(ScriptType.P2PKH);
|
|
||||||
wallet.setName(device.getModel().toDisplayString());
|
|
||||||
wallet.setPolicyType(PolicyType.SINGLE);
|
|
||||||
wallet.setScriptType(scriptType);
|
|
||||||
wallet.getKeystores().add(keystore);
|
|
||||||
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), null));
|
|
||||||
|
|
||||||
EventManager.get().post(new WalletImportEvent(wallet));
|
|
||||||
} else {
|
|
||||||
EventManager.get().post(new KeystoreImportEvent(keystore));
|
|
||||||
}
|
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
setError("Could not retrieve xpub", e.getMessage());
|
setError("Could not retrieve xpub", e.getMessage());
|
||||||
}
|
}
|
||||||
|
@ -624,35 +640,29 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
getXpubService.start();
|
getXpubService.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void importKeystore(List<ChildNumber> derivation, Keystore keystore) {
|
||||||
|
if(wallet.getScriptType() == null) {
|
||||||
|
ScriptType scriptType = Arrays.stream(ScriptType.ADDRESSABLE_TYPES).filter(type -> type.getDefaultDerivation().get(0).equals(derivation.get(0))).findFirst().orElse(ScriptType.P2PKH);
|
||||||
|
wallet.setName(device.getModel().toDisplayString());
|
||||||
|
wallet.setPolicyType(PolicyType.SINGLE);
|
||||||
|
wallet.setScriptType(scriptType);
|
||||||
|
wallet.getKeystores().add(keystore);
|
||||||
|
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), null));
|
||||||
|
|
||||||
|
EventManager.get().post(new WalletImportEvent(wallet));
|
||||||
|
} else {
|
||||||
|
EventManager.get().post(new KeystoreImportEvent(keystore));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sign() {
|
private void sign() {
|
||||||
if(device.isCard()) {
|
if(device.isCard()) {
|
||||||
if(pin.get().length() < 6) {
|
|
||||||
setDescription(pin.get().isEmpty() ? "Enter PIN code" : "PIN code too short");
|
|
||||||
setContent(getCardPinEntry());
|
|
||||||
setExpanded(true);
|
|
||||||
signButton.setDisable(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CardApi cardApi = new CardApi(pin.get());
|
CardApi cardApi = new CardApi(pin.get());
|
||||||
|
Service<PSBT> signService = cardApi.getSignService(wallet, psbt, messageProperty);
|
||||||
Service<Void> signService = cardApi.getSignService(wallet, psbt, messageProperty);
|
handleCardOperation(signService, signButton, "Signing", event -> {
|
||||||
signService.setOnSucceeded(event -> {
|
EventManager.get().post(new PSBTSignedEvent(psbt, signService.getValue()));
|
||||||
EventManager.get().post(new PSBTSignedEvent(psbt, psbt));
|
|
||||||
});
|
});
|
||||||
signService.setOnFailed(event -> {
|
|
||||||
Throwable rootCause = Throwables.getRootCause(event.getSource().getException());
|
|
||||||
if(rootCause instanceof CardAuthorizationException) {
|
|
||||||
setError(rootCause.getMessage(), null);
|
|
||||||
setContent(getCardPinEntry());
|
|
||||||
} else {
|
|
||||||
log.error("Signing Error: " + rootCause.getMessage(), event.getSource().getException());
|
|
||||||
setError("Signing Error", rootCause.getMessage());
|
|
||||||
}
|
|
||||||
signButton.setDisable(false);
|
|
||||||
});
|
|
||||||
signService.start();
|
|
||||||
} catch(Exception e) {
|
} catch(Exception e) {
|
||||||
log.error("Signing Error: " + e.getMessage(), e);
|
log.error("Signing Error: " + e.getMessage(), e);
|
||||||
setError("Signing Error", e.getMessage());
|
setError("Signing Error", e.getMessage());
|
||||||
|
@ -675,6 +685,31 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleCardOperation(Service<?> service, ButtonBase operationButton, String operationDescription, EventHandler<WorkerStateEvent> successHandler) {
|
||||||
|
if(pin.get().length() < 6) {
|
||||||
|
setDescription(pin.get().isEmpty() ? "Enter PIN code" : "PIN code too short");
|
||||||
|
setContent(getCardPinEntry(operationButton));
|
||||||
|
showHideLink.setVisible(false);
|
||||||
|
setExpanded(true);
|
||||||
|
operationButton.setDisable(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
service.setOnSucceeded(successHandler);
|
||||||
|
service.setOnFailed(event -> {
|
||||||
|
Throwable rootCause = Throwables.getRootCause(event.getSource().getException());
|
||||||
|
if(rootCause instanceof CardAuthorizationException) {
|
||||||
|
setError(rootCause.getMessage(), null);
|
||||||
|
setContent(getCardPinEntry(operationButton));
|
||||||
|
} else {
|
||||||
|
log.error(operationDescription + " Error: " + rootCause.getMessage(), event.getSource().getException());
|
||||||
|
setError(operationDescription + " Error", rootCause.getMessage());
|
||||||
|
}
|
||||||
|
operationButton.setDisable(false);
|
||||||
|
});
|
||||||
|
service.start();
|
||||||
|
}
|
||||||
|
|
||||||
private void displayAddress() {
|
private void displayAddress() {
|
||||||
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), outputDescriptor);
|
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), outputDescriptor);
|
||||||
displayAddressService.setOnSucceeded(successEvent -> {
|
displayAddressService.setOnSucceeded(successEvent -> {
|
||||||
|
@ -793,7 +828,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
TextField derivationField = new TextField();
|
TextField derivationField = new TextField();
|
||||||
derivationField.setPromptText("Derivation path");
|
derivationField.setPromptText("Derivation path");
|
||||||
derivationField.setText(KeyDerivation.writePath(derivation));
|
derivationField.setText(KeyDerivation.writePath(derivation));
|
||||||
derivationField.setDisable(keyDerivation != null);
|
derivationField.setDisable(device.isCard() || keyDerivation != null);
|
||||||
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
HBox.setHgrow(derivationField, Priority.ALWAYS);
|
||||||
|
|
||||||
ValidationSupport validationSupport = new ValidationSupport();
|
ValidationSupport validationSupport = new ValidationSupport();
|
||||||
|
@ -828,14 +863,63 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
return contentBox;
|
return contentBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Node getCardPinEntry() {
|
private Node getCardInitializationPanel(KeystoreCardImport importer) {
|
||||||
|
VBox initTypeBox = new VBox(5);
|
||||||
|
RadioButton automatic = new RadioButton("Automatic (Recommended)");
|
||||||
|
RadioButton advanced = new RadioButton("Advanced");
|
||||||
|
TextField entropy = new TextField();
|
||||||
|
entropy.setPromptText("Enter input for chain code");
|
||||||
|
entropy.setDisable(true);
|
||||||
|
|
||||||
|
ToggleGroup toggleGroup = new ToggleGroup();
|
||||||
|
automatic.setToggleGroup(toggleGroup);
|
||||||
|
advanced.setToggleGroup(toggleGroup);
|
||||||
|
automatic.setSelected(true);
|
||||||
|
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
|
entropy.setDisable(newValue == automatic);
|
||||||
|
});
|
||||||
|
|
||||||
|
initTypeBox.getChildren().addAll(automatic, advanced, entropy);
|
||||||
|
|
||||||
|
Button initializeButton = new Button("Initialize");
|
||||||
|
initializeButton.setDefaultButton(true);
|
||||||
|
initializeButton.setOnAction(event -> {
|
||||||
|
byte[] chainCode = toggleGroup.getSelectedToggle() == automatic ? null : Sha256Hash.hashTwice(entropy.getText().getBytes(StandardCharsets.UTF_8));
|
||||||
|
CardImportPane.CardInitializationService cardInitializationService = new CardImportPane.CardInitializationService(importer, chainCode);
|
||||||
|
cardInitializationService.setOnSucceeded(event1 -> {
|
||||||
|
AppServices.showSuccessDialog("Card Initialized", "The card was successfully initialized.\n\nYou will now need to enter the PIN code found on the back. You can change the PIN code once it has been imported.");
|
||||||
|
setDescription("Enter PIN code");
|
||||||
|
setContent(getCardPinEntry(importButton));
|
||||||
|
importButton.setDisable(false);
|
||||||
|
setExpanded(true);
|
||||||
|
});
|
||||||
|
cardInitializationService.setOnFailed(event1 -> {
|
||||||
|
Throwable e = event1.getSource().getException();
|
||||||
|
log.error("Error initializing card", e);
|
||||||
|
AppServices.showErrorDialog("Card Initialization Failed", "The card was not initialized.\n\n" + e.getMessage());
|
||||||
|
});
|
||||||
|
cardInitializationService.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
HBox contentBox = new HBox(20);
|
||||||
|
contentBox.getChildren().addAll(initTypeBox, initializeButton);
|
||||||
|
contentBox.setPadding(new Insets(10, 30, 10, 30));
|
||||||
|
HBox.setHgrow(initTypeBox, Priority.ALWAYS);
|
||||||
|
|
||||||
|
return contentBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node getCardPinEntry(ButtonBase operationButton) {
|
||||||
VBox vBox = new VBox();
|
VBox vBox = new VBox();
|
||||||
|
|
||||||
CustomPasswordField pinField = new ViewPasswordField();
|
CustomPasswordField pinField = new ViewPasswordField();
|
||||||
pinField.setPromptText("PIN Code");
|
pinField.setPromptText("PIN Code");
|
||||||
signButton.setDefaultButton(true);
|
if(operationButton instanceof Button defaultButton) {
|
||||||
|
defaultButton.setDefaultButton(true);
|
||||||
|
}
|
||||||
pin.bind(pinField.textProperty());
|
pin.bind(pinField.textProperty());
|
||||||
HBox.setHgrow(pinField, Priority.ALWAYS);
|
HBox.setHgrow(pinField, Priority.ALWAYS);
|
||||||
|
Platform.runLater(pinField::requestFocus);
|
||||||
|
|
||||||
HBox contentBox = new HBox();
|
HBox contentBox = new HBox();
|
||||||
contentBox.setAlignment(Pos.TOP_RIGHT);
|
contentBox.setAlignment(Pos.TOP_RIGHT);
|
||||||
|
|
|
@ -32,31 +32,6 @@ public class DeviceSignDialog extends DeviceDialog<PSBT> {
|
||||||
setResultConverter(dialogButton -> dialogButton.getButtonData().isCancelButton() ? null : psbt);
|
setResultConverter(dialogButton -> dialogButton.getButtonData().isCancelButton() ? null : psbt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Device> getDevices() {
|
|
||||||
List<Device> devices = super.getDevices();
|
|
||||||
|
|
||||||
if(CardApi.isReaderAvailable()) {
|
|
||||||
devices = new ArrayList<>(devices);
|
|
||||||
try {
|
|
||||||
CardApi cardApi = new CardApi(null);
|
|
||||||
if(cardApi.isInitialized()) {
|
|
||||||
Device cardDevice = new Device();
|
|
||||||
cardDevice.setType(WalletModel.TAPSIGNER.getType());
|
|
||||||
cardDevice.setModel(WalletModel.TAPSIGNER);
|
|
||||||
cardDevice.setNeedsPassphraseSent(Boolean.FALSE);
|
|
||||||
cardDevice.setNeedsPinSent(Boolean.FALSE);
|
|
||||||
cardDevice.setCard(true);
|
|
||||||
devices.add(cardDevice);
|
|
||||||
}
|
|
||||||
} catch(CardException e) {
|
|
||||||
log.error("Error reading card", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DevicePane getDevicePane(Device device, boolean defaultDevice) {
|
protected DevicePane getDevicePane(Device device, boolean defaultDevice) {
|
||||||
return new DevicePane(wallet, psbt, device, defaultDevice);
|
return new DevicePane(wallet, psbt, device, defaultDevice);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
import com.sparrowwallet.drongo.wallet.StandardAccount;
|
||||||
import com.sparrowwallet.drongo.wallet.WalletModel;
|
import com.sparrowwallet.drongo.wallet.WalletModel;
|
||||||
|
import com.sparrowwallet.sparrow.io.ckcard.CardApi;
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Service;
|
import javafx.concurrent.Service;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
|
@ -17,6 +18,8 @@ import org.controlsfx.tools.Platform;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.smartcardio.CardException;
|
||||||
|
import javax.smartcardio.CardNotPresentException;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -40,6 +43,13 @@ public class Hwi {
|
||||||
private static boolean isPromptActive = false;
|
private static boolean isPromptActive = false;
|
||||||
|
|
||||||
public List<Device> enumerate(String passphrase) throws ImportException {
|
public List<Device> enumerate(String passphrase) throws ImportException {
|
||||||
|
List<Device> devices = new ArrayList<>();
|
||||||
|
devices.addAll(enumerateUsb(passphrase));
|
||||||
|
devices.addAll(enumerateCard());
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Device> enumerateUsb(String passphrase) throws ImportException {
|
||||||
String output = null;
|
String output = null;
|
||||||
try {
|
try {
|
||||||
List<String> command;
|
List<String> command;
|
||||||
|
@ -84,6 +94,30 @@ public class Hwi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<Device> enumerateCard() {
|
||||||
|
List<Device> devices = new ArrayList<>();
|
||||||
|
if(CardApi.isReaderAvailable()) {
|
||||||
|
try {
|
||||||
|
CardApi cardApi = new CardApi(null);
|
||||||
|
WalletModel walletModel = cardApi.getCardType();
|
||||||
|
|
||||||
|
Device cardDevice = new Device();
|
||||||
|
cardDevice.setType(walletModel.getType());
|
||||||
|
cardDevice.setModel(walletModel);
|
||||||
|
cardDevice.setNeedsPassphraseSent(Boolean.FALSE);
|
||||||
|
cardDevice.setNeedsPinSent(Boolean.FALSE);
|
||||||
|
cardDevice.setCard(true);
|
||||||
|
devices.add(cardDevice);
|
||||||
|
} catch(CardNotPresentException e) {
|
||||||
|
//ignore
|
||||||
|
} catch(CardException e) {
|
||||||
|
log.error("Error reading card", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean promptPin(Device device) throws ImportException {
|
public boolean promptPin(Device device) throws ImportException {
|
||||||
try {
|
try {
|
||||||
String output = execute(getDeviceCommand(device, Command.PROMPT_PIN));
|
String output = execute(getDeviceCommand(device, Command.PROMPT_PIN));
|
||||||
|
|
|
@ -54,6 +54,11 @@ public class CardApi {
|
||||||
cardProtocol.setup(cvc, chainCode);
|
cardProtocol.setup(cvc, chainCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WalletModel getCardType() throws CardException {
|
||||||
|
CardStatus cardStatus = getStatus();
|
||||||
|
return cardStatus.getCardType();
|
||||||
|
}
|
||||||
|
|
||||||
CardStatus getStatus() throws CardException {
|
CardStatus getStatus() throws CardException {
|
||||||
CardStatus cardStatus = cardProtocol.getStatus();
|
CardStatus cardStatus = cardProtocol.getStatus();
|
||||||
if(cardStatus.getCardType() != cardType) {
|
if(cardStatus.getCardType() != cardType) {
|
||||||
|
@ -139,7 +144,7 @@ public class CardApi {
|
||||||
return Utils.bytesToHex(masterXpubkey.getKey().getFingerprint());
|
return Utils.bytesToHex(masterXpubkey.getKey().getFingerprint());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Service<Void> getSignService(Wallet wallet, PSBT psbt, StringProperty messageProperty) {
|
public Service<PSBT> getSignService(Wallet wallet, PSBT psbt, StringProperty messageProperty) {
|
||||||
return new SignService(wallet, psbt, messageProperty);
|
return new SignService(wallet, psbt, messageProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,7 +235,7 @@ public class CardApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SignService extends Service<Void> {
|
public class SignService extends Service<PSBT> {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final PSBT psbt;
|
private final PSBT psbt;
|
||||||
private final StringProperty messageProperty;
|
private final StringProperty messageProperty;
|
||||||
|
@ -242,15 +247,15 @@ public class CardApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Task<Void> createTask() {
|
protected Task<PSBT> createTask() {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
@Override
|
@Override
|
||||||
protected Void call() throws Exception {
|
protected PSBT call() throws Exception {
|
||||||
CardStatus cardStatus = getStatus();
|
CardStatus cardStatus = getStatus();
|
||||||
checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
||||||
|
|
||||||
sign(wallet, psbt);
|
sign(wallet, psbt);
|
||||||
return null;
|
return psbt;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ public class CardProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new CardSignFailedException("Failed to sign digest after 5 tries.");
|
throw new CardSignFailedException("Failed to sign digest after 5 tries. It's safe to try again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public CardChange change(String currentCvc, String newCvc) throws CardException {
|
public CardChange change(String currentCvc, String newCvc) throws CardException {
|
||||||
|
|
Loading…
Reference in a new issue