mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
upgrade to hwi-2.0.0-rc.2, display addresses on usb devices for multisig wallets
This commit is contained in:
parent
58a31f435e
commit
f0c239d625
9 changed files with 128 additions and 79 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
||||||
Subproject commit 533791edcf38a6ca05857483df15a5c54a4554f0
|
Subproject commit e974c3f4223d1771aa59013c31311cf3d85e0915
|
|
@ -1,22 +1,22 @@
|
||||||
package com.sparrowwallet.sparrow.control;
|
package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.event.AddressDisplayedEvent;
|
import com.sparrowwallet.sparrow.event.AddressDisplayedEvent;
|
||||||
import com.sparrowwallet.sparrow.io.Device;
|
import com.sparrowwallet.sparrow.io.Device;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DeviceAddressDialog extends DeviceDialog<String> {
|
public class DeviceAddressDialog extends DeviceDialog<String> {
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final KeyDerivation keyDerivation;
|
private final OutputDescriptor outputDescriptor;
|
||||||
|
|
||||||
public DeviceAddressDialog(List<String> operationFingerprints, Wallet wallet, KeyDerivation keyDerivation) {
|
public DeviceAddressDialog(Wallet wallet, OutputDescriptor outputDescriptor) {
|
||||||
super(operationFingerprints);
|
super(outputDescriptor.getExtendedPublicKeys().stream().map(extKey -> outputDescriptor.getKeyDerivation(extKey).getMasterFingerprint()).collect(Collectors.toList()));
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.keyDerivation = keyDerivation;
|
this.outputDescriptor = outputDescriptor;
|
||||||
|
|
||||||
EventManager.get().register(this);
|
EventManager.get().register(this);
|
||||||
setOnCloseRequest(event -> {
|
setOnCloseRequest(event -> {
|
||||||
|
@ -26,7 +26,7 @@ public class DeviceAddressDialog extends DeviceDialog<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DevicePane getDevicePane(Device device) {
|
protected DevicePane getDevicePane(Device device) {
|
||||||
return new DevicePane(wallet, keyDerivation, device);
|
return new DevicePane(wallet, outputDescriptor, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
|
|
@ -101,6 +101,7 @@ public abstract class DeviceDialog<R> extends Dialog<R> {
|
||||||
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
Platform.runLater(() -> EventManager.get().post(new UsbDeviceEvent(devices)));
|
||||||
});
|
});
|
||||||
enumerateService.setOnFailed(workerStateEvent -> {
|
enumerateService.setOnFailed(workerStateEvent -> {
|
||||||
|
deviceAccordion.getPanes().clear();
|
||||||
scanBox.setVisible(true);
|
scanBox.setVisible(true);
|
||||||
scanLabel.setText(workerStateEvent.getSource().getException().getMessage());
|
scanLabel.setText(workerStateEvent.getSource().getException().getMessage());
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow.control;
|
||||||
|
|
||||||
import com.sparrowwallet.drongo.ExtendedKey;
|
import com.sparrowwallet.drongo.ExtendedKey;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
|
@ -15,7 +16,6 @@ import com.sparrowwallet.sparrow.event.MessageSignedEvent;
|
||||||
import com.sparrowwallet.sparrow.event.PSBTSignedEvent;
|
import com.sparrowwallet.sparrow.event.PSBTSignedEvent;
|
||||||
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.drongo.wallet.WalletModel;
|
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
@ -25,7 +25,6 @@ import javafx.scene.Node;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
import org.controlsfx.control.textfield.CustomPasswordField;
|
import org.controlsfx.control.textfield.CustomPasswordField;
|
||||||
import org.controlsfx.control.textfield.CustomTextField;
|
|
||||||
import org.controlsfx.control.textfield.TextFields;
|
import org.controlsfx.control.textfield.TextFields;
|
||||||
import org.controlsfx.glyphfont.Glyph;
|
import org.controlsfx.glyphfont.Glyph;
|
||||||
import org.controlsfx.validation.ValidationResult;
|
import org.controlsfx.validation.ValidationResult;
|
||||||
|
@ -36,6 +35,7 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class DevicePane extends TitledDescriptionPane {
|
public class DevicePane extends TitledDescriptionPane {
|
||||||
private static final Logger log = LoggerFactory.getLogger(DevicePane.class);
|
private static final Logger log = LoggerFactory.getLogger(DevicePane.class);
|
||||||
|
@ -43,6 +43,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
private final DeviceOperation deviceOperation;
|
private final DeviceOperation deviceOperation;
|
||||||
private final Wallet wallet;
|
private final Wallet wallet;
|
||||||
private final PSBT psbt;
|
private final PSBT psbt;
|
||||||
|
private final OutputDescriptor outputDescriptor;
|
||||||
private final KeyDerivation keyDerivation;
|
private final KeyDerivation keyDerivation;
|
||||||
private final String message;
|
private final String message;
|
||||||
private final Device device;
|
private final Device device;
|
||||||
|
@ -63,6 +64,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
this.deviceOperation = DeviceOperation.IMPORT;
|
this.deviceOperation = DeviceOperation.IMPORT;
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
|
this.outputDescriptor = null;
|
||||||
this.keyDerivation = null;
|
this.keyDerivation = null;
|
||||||
this.message = null;
|
this.message = null;
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
@ -83,6 +85,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
this.deviceOperation = DeviceOperation.SIGN;
|
this.deviceOperation = DeviceOperation.SIGN;
|
||||||
this.wallet = null;
|
this.wallet = null;
|
||||||
this.psbt = psbt;
|
this.psbt = psbt;
|
||||||
|
this.outputDescriptor = null;
|
||||||
this.keyDerivation = null;
|
this.keyDerivation = null;
|
||||||
this.message = null;
|
this.message = null;
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
@ -98,12 +101,13 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
buttonBox.getChildren().addAll(setPassphraseButton, signButton);
|
buttonBox.getChildren().addAll(setPassphraseButton, signButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DevicePane(Wallet wallet, KeyDerivation keyDerivation, Device device) {
|
public DevicePane(Wallet wallet, OutputDescriptor outputDescriptor, Device device) {
|
||||||
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
super(device.getModel().toDisplayString(), "", "", "image/" + device.getType() + ".png");
|
||||||
this.deviceOperation = DeviceOperation.DISPLAY_ADDRESS;
|
this.deviceOperation = DeviceOperation.DISPLAY_ADDRESS;
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
this.keyDerivation = keyDerivation;
|
this.outputDescriptor = outputDescriptor;
|
||||||
|
this.keyDerivation = null;
|
||||||
this.message = null;
|
this.message = null;
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
|
||||||
|
@ -123,6 +127,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
this.deviceOperation = DeviceOperation.SIGN_MESSAGE;
|
this.deviceOperation = DeviceOperation.SIGN_MESSAGE;
|
||||||
this.wallet = wallet;
|
this.wallet = wallet;
|
||||||
this.psbt = null;
|
this.psbt = null;
|
||||||
|
this.outputDescriptor = null;
|
||||||
this.keyDerivation = keyDerivation;
|
this.keyDerivation = keyDerivation;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.device = device;
|
this.device = device;
|
||||||
|
@ -233,7 +238,8 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
displayAddressButton.managedProperty().bind(displayAddressButton.visibleProperty());
|
displayAddressButton.managedProperty().bind(displayAddressButton.visibleProperty());
|
||||||
displayAddressButton.setVisible(false);
|
displayAddressButton.setVisible(false);
|
||||||
|
|
||||||
if(device.getFingerprint() != null && !device.getFingerprint().equals(keyDerivation.getMasterFingerprint())) {
|
List<String> fingerprints = outputDescriptor.getExtendedPublicKeys().stream().map(extKey -> outputDescriptor.getKeyDerivation(extKey).getMasterFingerprint()).collect(Collectors.toList());
|
||||||
|
if(device.getFingerprint() != null && !fingerprints.contains(device.getFingerprint())) {
|
||||||
displayAddressButton.setDisable(true);
|
displayAddressButton.setDisable(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -471,7 +477,7 @@ public class DevicePane extends TitledDescriptionPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayAddress() {
|
private void displayAddress() {
|
||||||
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), keyDerivation.getDerivationPath());
|
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), outputDescriptor);
|
||||||
displayAddressService.setOnSucceeded(successEvent -> {
|
displayAddressService.setOnSucceeded(successEvent -> {
|
||||||
String address = displayAddressService.getValue();
|
String address = displayAddressService.getValue();
|
||||||
EventManager.get().post(new AddressDisplayedEvent(address));
|
EventManager.get().post(new AddressDisplayedEvent(address));
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
import com.google.gson.*;
|
import com.google.gson.*;
|
||||||
import com.sparrowwallet.drongo.Network;
|
import com.sparrowwallet.drongo.Network;
|
||||||
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
import com.sparrowwallet.drongo.protocol.ScriptType;
|
import com.sparrowwallet.drongo.protocol.ScriptType;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||||
|
@ -29,7 +30,7 @@ import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
public class Hwi {
|
public class Hwi {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Hwi.class);
|
private static final Logger log = LoggerFactory.getLogger(Hwi.class);
|
||||||
private static final String TEMP_FILE_PREFIX = "hwi-1.2.1-";
|
private static final String VERSION_PREFIX = "hwi-2.0.0-rc.2";
|
||||||
|
|
||||||
private static boolean isPromptActive = false;
|
private static boolean isPromptActive = false;
|
||||||
|
|
||||||
|
@ -44,6 +45,9 @@ public class Hwi {
|
||||||
|
|
||||||
String output = execute(command);
|
String output = execute(command);
|
||||||
Device[] devices = getGson().fromJson(output, Device[].class);
|
Device[] devices = getGson().fromJson(output, Device[].class);
|
||||||
|
if(devices == null) {
|
||||||
|
throw new ImportException("Error scanning, check devices are ready");
|
||||||
|
}
|
||||||
return Arrays.stream(devices).filter(device -> device != null && device.getModel() != null).collect(Collectors.toList());
|
return Arrays.stream(devices).filter(device -> device != null && device.getModel() != null).collect(Collectors.toList());
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
throw new ImportException(e);
|
throw new ImportException(e);
|
||||||
|
@ -83,33 +87,41 @@ public class Hwi {
|
||||||
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
|
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
|
||||||
if(result.get("xpub") != null) {
|
if(result.get("xpub") != null) {
|
||||||
return result.get("xpub").getAsString();
|
return result.get("xpub").getAsString();
|
||||||
|
} else {
|
||||||
|
JsonElement error = result.get("error");
|
||||||
|
if(error != null) {
|
||||||
|
throw new ImportException(error.getAsString());
|
||||||
} else {
|
} else {
|
||||||
throw new ImportException("Could not retrieve xpub - reconnect your device and try again.");
|
throw new ImportException("Could not retrieve xpub - reconnect your device and try again.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
throw new ImportException(e);
|
throw new ImportException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String displayAddress(Device device, String passphrase, ScriptType scriptType, String derivationPath) throws DisplayAddressException {
|
public String displayAddress(Device device, String passphrase, ScriptType scriptType, OutputDescriptor outputDescriptor) throws DisplayAddressException {
|
||||||
try {
|
try {
|
||||||
if(!List.of(ScriptType.P2PKH, ScriptType.P2SH_P2WPKH, ScriptType.P2WPKH).contains(scriptType)) {
|
if(!Arrays.asList(ScriptType.SINGLE_HASH_TYPES).contains(scriptType)) {
|
||||||
throw new IllegalArgumentException("Cannot display address for script type " + scriptType + ": Only single sig types supported");
|
throw new IllegalArgumentException("Cannot display address for script type " + scriptType + ": Only single hash types supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
String type = null;
|
String descriptor = outputDescriptor.toString().replace("sortedmulti", "multi");
|
||||||
|
System.out.println(descriptor);
|
||||||
|
|
||||||
|
String addrType = "legacy";
|
||||||
if(scriptType == ScriptType.P2SH_P2WPKH) {
|
if(scriptType == ScriptType.P2SH_P2WPKH) {
|
||||||
type = "--sh_wpkh";
|
addrType = "sh_wit";
|
||||||
} else if(scriptType == ScriptType.P2WPKH) {
|
} else if(scriptType == ScriptType.P2WPKH) {
|
||||||
type = "--wpkh";
|
addrType = "wit";
|
||||||
}
|
}
|
||||||
|
|
||||||
isPromptActive = false;
|
isPromptActive = false;
|
||||||
String output;
|
String output;
|
||||||
if(passphrase != null && !passphrase.isEmpty() && device.getModel().externalPassphraseEntry()) {
|
if(passphrase != null && !passphrase.isEmpty() && device.getModel().externalPassphraseEntry()) {
|
||||||
output = execute(getDeviceCommand(device, passphrase, Command.DISPLAY_ADDRESS, "--path", derivationPath, type));
|
output = execute(getDeviceCommand(device, passphrase, Command.DISPLAY_ADDRESS, "--desc", descriptor, "--addr-type", addrType));
|
||||||
} else {
|
} else {
|
||||||
output = execute(getDeviceCommand(device, Command.DISPLAY_ADDRESS, "--path", derivationPath, type));
|
output = execute(getDeviceCommand(device, Command.DISPLAY_ADDRESS, "--desc", descriptor, "--addr-type", addrType));
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
|
JsonObject result = JsonParser.parseString(output).getAsJsonObject();
|
||||||
|
@ -199,7 +211,7 @@ public class Hwi {
|
||||||
private synchronized File getHwiExecutable(Command command) {
|
private synchronized File getHwiExecutable(Command command) {
|
||||||
File hwiExecutable = Config.get().getHwi();
|
File hwiExecutable = Config.get().getHwi();
|
||||||
if(hwiExecutable != null && hwiExecutable.exists()) {
|
if(hwiExecutable != null && hwiExecutable.exists()) {
|
||||||
if(command.isTestFirst() && (!hwiExecutable.getAbsolutePath().contains(TEMP_FILE_PREFIX) || !testHwi(hwiExecutable))) {
|
if(command.isTestFirst() && (!hwiExecutable.getAbsolutePath().contains(VERSION_PREFIX) || !testHwi(hwiExecutable))) {
|
||||||
if(Platform.getCurrent() == Platform.OSX) {
|
if(Platform.getCurrent() == Platform.OSX) {
|
||||||
deleteDirectory(hwiExecutable.getParentFile());
|
deleteDirectory(hwiExecutable.getParentFile());
|
||||||
} else {
|
} else {
|
||||||
|
@ -218,8 +230,8 @@ public class Hwi {
|
||||||
//The check will still happen on first invocation, but will not thereafter
|
//The check will still happen on first invocation, but will not thereafter
|
||||||
//See https://github.com/bitcoin-core/HWI/issues/327 for details
|
//See https://github.com/bitcoin-core/HWI/issues/327 for details
|
||||||
if(platform == Platform.OSX) {
|
if(platform == Platform.OSX) {
|
||||||
InputStream inputStream = Hwi.class.getResourceAsStream("/native/osx/x64/hwi-1.2.1-mac-amd64-signed.zip");
|
InputStream inputStream = Hwi.class.getResourceAsStream("/native/osx/x64/" + VERSION_PREFIX + "-mac-amd64-signed.zip");
|
||||||
Path tempHwiDirPath = Files.createTempDirectory(TEMP_FILE_PREFIX, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
Path tempHwiDirPath = Files.createTempDirectory(VERSION_PREFIX, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
||||||
File tempHwiDir = tempHwiDirPath.toFile();
|
File tempHwiDir = tempHwiDirPath.toFile();
|
||||||
//tempHwiDir.deleteOnExit();
|
//tempHwiDir.deleteOnExit();
|
||||||
log.debug("Using temp HWI path: " + tempHwiDir.getAbsolutePath());
|
log.debug("Using temp HWI path: " + tempHwiDir.getAbsolutePath());
|
||||||
|
@ -227,7 +239,10 @@ public class Hwi {
|
||||||
File tempExec = null;
|
File tempExec = null;
|
||||||
ZipInputStream zis = new ZipInputStream(inputStream);
|
ZipInputStream zis = new ZipInputStream(inputStream);
|
||||||
ZipEntry zipEntry = zis.getNextEntry();
|
ZipEntry zipEntry = zis.getNextEntry();
|
||||||
while (zipEntry != null) {
|
while(zipEntry != null) {
|
||||||
|
if(zipEntry.isDirectory()) {
|
||||||
|
newDirectory(tempHwiDir, zipEntry, ownerExecutableWritable);
|
||||||
|
} else {
|
||||||
File newFile = newFile(tempHwiDir, zipEntry, ownerExecutableWritable);
|
File newFile = newFile(tempHwiDir, zipEntry, ownerExecutableWritable);
|
||||||
//newFile.deleteOnExit();
|
//newFile.deleteOnExit();
|
||||||
FileOutputStream fos = new FileOutputStream(newFile);
|
FileOutputStream fos = new FileOutputStream(newFile);
|
||||||
|
@ -235,9 +250,10 @@ public class Hwi {
|
||||||
fos.flush();
|
fos.flush();
|
||||||
fos.close();
|
fos.close();
|
||||||
|
|
||||||
if (zipEntry.getName().equals("hwi")) {
|
if(zipEntry.getName().equals("hwi")) {
|
||||||
tempExec = newFile;
|
tempExec = newFile;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
zipEntry = zis.getNextEntry();
|
zipEntry = zis.getNextEntry();
|
||||||
}
|
}
|
||||||
|
@ -250,10 +266,10 @@ public class Hwi {
|
||||||
Path tempExecPath;
|
Path tempExecPath;
|
||||||
if(platform == Platform.WINDOWS) {
|
if(platform == Platform.WINDOWS) {
|
||||||
inputStream = Hwi.class.getResourceAsStream("/native/windows/x64/hwi.exe");
|
inputStream = Hwi.class.getResourceAsStream("/native/windows/x64/hwi.exe");
|
||||||
tempExecPath = Files.createTempFile(TEMP_FILE_PREFIX, null);
|
tempExecPath = Files.createTempFile(VERSION_PREFIX, null);
|
||||||
} else {
|
} else {
|
||||||
inputStream = Hwi.class.getResourceAsStream("/native/linux/x64/hwi");
|
inputStream = Hwi.class.getResourceAsStream("/native/linux/x64/hwi");
|
||||||
tempExecPath = Files.createTempFile(TEMP_FILE_PREFIX, null, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
tempExecPath = Files.createTempFile(VERSION_PREFIX, null, PosixFilePermissions.asFileAttribute(ownerExecutableWritable));
|
||||||
}
|
}
|
||||||
|
|
||||||
File tempExec = tempExecPath.toFile();
|
File tempExec = tempExecPath.toFile();
|
||||||
|
@ -299,6 +315,20 @@ public class Hwi {
|
||||||
return directoryToBeDeleted.delete();
|
return directoryToBeDeleted.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static File newDirectory(File destinationDir, ZipEntry zipEntry, Set<PosixFilePermission> setFilePermissions) throws IOException {
|
||||||
|
String destDirPath = destinationDir.getCanonicalPath();
|
||||||
|
|
||||||
|
Path path = Path.of(destDirPath, zipEntry.getName());
|
||||||
|
File destDir = Files.createDirectory(path, PosixFilePermissions.asFileAttribute(setFilePermissions)).toFile();
|
||||||
|
|
||||||
|
String destSubDirPath = destDir.getCanonicalPath();
|
||||||
|
if(!destSubDirPath.startsWith(destDirPath + File.separator)) {
|
||||||
|
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return destDir;
|
||||||
|
}
|
||||||
|
|
||||||
public static File newFile(File destinationDir, ZipEntry zipEntry, Set<PosixFilePermission> setFilePermissions) throws IOException {
|
public static File newFile(File destinationDir, ZipEntry zipEntry, Set<PosixFilePermission> setFilePermissions) throws IOException {
|
||||||
String destDirPath = destinationDir.getCanonicalPath();
|
String destDirPath = destinationDir.getCanonicalPath();
|
||||||
|
|
||||||
|
@ -306,7 +336,7 @@ public class Hwi {
|
||||||
File destFile = Files.createFile(path, PosixFilePermissions.asFileAttribute(setFilePermissions)).toFile();
|
File destFile = Files.createFile(path, PosixFilePermissions.asFileAttribute(setFilePermissions)).toFile();
|
||||||
|
|
||||||
String destFilePath = destFile.getCanonicalPath();
|
String destFilePath = destFile.getCanonicalPath();
|
||||||
if (!destFilePath.startsWith(destDirPath + File.separator)) {
|
if(!destFilePath.startsWith(destDirPath + File.separator)) {
|
||||||
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
|
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,30 +354,41 @@ public class Hwi {
|
||||||
|
|
||||||
private List<String> getDeviceCommand(Device device, Command command) throws IOException {
|
private List<String> getDeviceCommand(Device device, Command command) throws IOException {
|
||||||
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()));
|
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()));
|
||||||
if(Network.get() != Network.MAINNET) {
|
addChainType(elements);
|
||||||
elements.add(elements.size() - 1, "--testnet");
|
|
||||||
}
|
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getDeviceCommand(Device device, Command command, String... commandData) throws IOException {
|
private List<String> getDeviceCommand(Device device, Command command, String... commandData) throws IOException {
|
||||||
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()));
|
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), command.toString()));
|
||||||
if(Network.get() != Network.MAINNET) {
|
addChainType(elements);
|
||||||
elements.add(elements.size() - 1, "--testnet");
|
|
||||||
}
|
|
||||||
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getDeviceCommand(Device device, String passphrase, Command command, String... commandData) throws IOException {
|
private List<String> getDeviceCommand(Device device, String passphrase, Command command, String... commandData) throws IOException {
|
||||||
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), "--password", passphrase, command.toString()));
|
List<String> elements = new ArrayList<>(List.of(getHwiExecutable(command).getAbsolutePath(), "--device-path", device.getPath(), "--device-type", device.getType(), "--password", passphrase, command.toString()));
|
||||||
if(Network.get() != Network.MAINNET) {
|
addChainType(elements);
|
||||||
elements.add(elements.size() - 1, "--testnet");
|
|
||||||
}
|
|
||||||
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
elements.addAll(Arrays.stream(commandData).filter(Objects::nonNull).collect(Collectors.toList()));
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addChainType(List<String> elements) {
|
||||||
|
if(Network.get() != Network.MAINNET) {
|
||||||
|
elements.add(elements.size() - 1, "--chain");
|
||||||
|
elements.add(elements.size() - 1, getChainName(Network.get()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getChainName(Network network) {
|
||||||
|
if(network == Network.MAINNET) {
|
||||||
|
return "main";
|
||||||
|
} else if(network == Network.TESTNET) {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
return network.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public static class EnumerateService extends Service<List<Device>> {
|
public static class EnumerateService extends Service<List<Device>> {
|
||||||
private final String passphrase;
|
private final String passphrase;
|
||||||
|
|
||||||
|
@ -430,13 +471,13 @@ public class Hwi {
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final String passphrase;
|
private final String passphrase;
|
||||||
private final ScriptType scriptType;
|
private final ScriptType scriptType;
|
||||||
private final String derivationPath;
|
private final OutputDescriptor outputDescriptor;
|
||||||
|
|
||||||
public DisplayAddressService(Device device, String passphrase, ScriptType scriptType, String derivationPath) {
|
public DisplayAddressService(Device device, String passphrase, ScriptType scriptType, OutputDescriptor outputDescriptor) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.passphrase = passphrase;
|
this.passphrase = passphrase;
|
||||||
this.scriptType = scriptType;
|
this.scriptType = scriptType;
|
||||||
this.derivationPath = derivationPath;
|
this.outputDescriptor = outputDescriptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -444,7 +485,7 @@ public class Hwi {
|
||||||
return new Task<>() {
|
return new Task<>() {
|
||||||
protected String call() throws DisplayAddressException {
|
protected String call() throws DisplayAddressException {
|
||||||
Hwi hwi = new Hwi();
|
Hwi hwi = new Hwi();
|
||||||
return hwi.displayAddress(device, passphrase, scriptType, derivationPath);
|
return hwi.displayAddress(device, passphrase, scriptType, outputDescriptor);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.google.zxing.common.BitMatrix;
|
||||||
import com.google.zxing.qrcode.QRCodeWriter;
|
import com.google.zxing.qrcode.QRCodeWriter;
|
||||||
import com.sparrowwallet.drongo.KeyDerivation;
|
import com.sparrowwallet.drongo.KeyDerivation;
|
||||||
import com.sparrowwallet.drongo.KeyPurpose;
|
import com.sparrowwallet.drongo.KeyPurpose;
|
||||||
import com.sparrowwallet.drongo.policy.PolicyType;
|
import com.sparrowwallet.drongo.OutputDescriptor;
|
||||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex;
|
||||||
import com.sparrowwallet.drongo.wallet.Keystore;
|
import com.sparrowwallet.drongo.wallet.Keystore;
|
||||||
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
import com.sparrowwallet.drongo.wallet.KeystoreSource;
|
||||||
|
@ -17,10 +17,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
|
||||||
import com.sparrowwallet.sparrow.AppServices;
|
import com.sparrowwallet.sparrow.AppServices;
|
||||||
import com.sparrowwallet.sparrow.EventManager;
|
import com.sparrowwallet.sparrow.EventManager;
|
||||||
import com.sparrowwallet.sparrow.control.*;
|
import com.sparrowwallet.sparrow.control.*;
|
||||||
import com.sparrowwallet.sparrow.event.ReceiveToEvent;
|
import com.sparrowwallet.sparrow.event.*;
|
||||||
import com.sparrowwallet.sparrow.event.UsbDeviceEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.WalletHistoryChangedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.event.WalletNodesChangedEvent;
|
|
||||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||||
import com.sparrowwallet.sparrow.io.Device;
|
import com.sparrowwallet.sparrow.io.Device;
|
||||||
import com.sparrowwallet.sparrow.io.Hwi;
|
import com.sparrowwallet.sparrow.io.Hwi;
|
||||||
|
@ -153,10 +150,11 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplayAddress(List<Device> devices) {
|
private void updateDisplayAddress(List<Device> devices) {
|
||||||
//Can only display address for single sig wallets. See https://github.com/bitcoin-core/HWI/issues/224
|
|
||||||
Wallet wallet = getWalletForm().getWallet();
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
if(wallet.getPolicyType().equals(PolicyType.SINGLE)) {
|
OutputDescriptor walletDescriptor = OutputDescriptor.getOutputDescriptor(walletForm.getWallet());
|
||||||
List<Device> addressDevices = devices.stream().filter(device -> wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint())).collect(Collectors.toList());
|
List<String> walletFingerprints = walletDescriptor.getExtendedPublicKeys().stream().map(extKey -> walletDescriptor.getKeyDerivation(extKey).getMasterFingerprint()).collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<Device> addressDevices = devices.stream().filter(device -> walletFingerprints.contains(device.getFingerprint())).collect(Collectors.toList());
|
||||||
if(addressDevices.isEmpty()) {
|
if(addressDevices.isEmpty()) {
|
||||||
addressDevices = devices.stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList());
|
addressDevices = devices.stream().filter(device -> device.getNeedsPinSent() || device.getNeedsPassphraseSent()).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
@ -173,7 +171,6 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
displayAddress.setUserData(null);
|
displayAddress.setUserData(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
displayAddress.setVisible(false);
|
displayAddress.setVisible(false);
|
||||||
displayAddress.setUserData(null);
|
displayAddress.setUserData(null);
|
||||||
|
@ -204,28 +201,27 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void displayAddress(ActionEvent event) {
|
public void displayAddress(ActionEvent event) {
|
||||||
Wallet wallet = getWalletForm().getWallet();
|
Wallet wallet = getWalletForm().getWallet();
|
||||||
if(wallet.getPolicyType() == PolicyType.SINGLE && currentEntry != null) {
|
if(currentEntry != null) {
|
||||||
Keystore keystore = wallet.getKeystores().get(0);
|
OutputDescriptor addressDescriptor = OutputDescriptor.getOutputDescriptor(walletForm.getWallet(), currentEntry.getNode().getKeyPurpose(), currentEntry.getNode().getIndex());
|
||||||
KeyDerivation fullDerivation = keystore.getKeyDerivation().extend(currentEntry.getNode().getDerivation());
|
|
||||||
|
|
||||||
List<Device> possibleDevices = (List<Device>)displayAddress.getUserData();
|
List<Device> possibleDevices = (List<Device>)displayAddress.getUserData();
|
||||||
if(possibleDevices != null && !possibleDevices.isEmpty()) {
|
if(possibleDevices != null && !possibleDevices.isEmpty()) {
|
||||||
if(possibleDevices.size() > 1 || possibleDevices.get(0).getNeedsPinSent() || possibleDevices.get(0).getNeedsPassphraseSent()) {
|
if(possibleDevices.size() > 1 || possibleDevices.get(0).getNeedsPinSent() || possibleDevices.get(0).getNeedsPassphraseSent()) {
|
||||||
DeviceAddressDialog dlg = new DeviceAddressDialog(List.of(keystore.getKeyDerivation().getMasterFingerprint()), wallet, fullDerivation);
|
DeviceAddressDialog dlg = new DeviceAddressDialog(wallet, addressDescriptor);
|
||||||
dlg.showAndWait();
|
dlg.showAndWait();
|
||||||
} else {
|
} else {
|
||||||
Device actualDevice = possibleDevices.get(0);
|
Device actualDevice = possibleDevices.get(0);
|
||||||
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(actualDevice, "", wallet.getScriptType(), fullDerivation.getDerivationPath());
|
Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(actualDevice, "", wallet.getScriptType(), addressDescriptor);
|
||||||
displayAddressService.setOnFailed(failedEvent -> {
|
displayAddressService.setOnFailed(failedEvent -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
DeviceAddressDialog dlg = new DeviceAddressDialog(List.of(keystore.getKeyDerivation().getMasterFingerprint()), wallet, fullDerivation);
|
DeviceAddressDialog dlg = new DeviceAddressDialog(wallet, addressDescriptor);
|
||||||
dlg.showAndWait();
|
dlg.showAndWait();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
displayAddressService.start();
|
displayAddressService.start();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DeviceAddressDialog dlg = new DeviceAddressDialog(List.of(keystore.getKeyDerivation().getMasterFingerprint()), wallet, fullDerivation);
|
DeviceAddressDialog dlg = new DeviceAddressDialog(wallet, addressDescriptor);
|
||||||
dlg.showAndWait();
|
dlg.showAndWait();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -261,6 +257,11 @@ public class ReceiveController extends WalletFormController implements Initializ
|
||||||
return duplicateGlyph;
|
return duplicateGlyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void walletSettingsChanged(WalletSettingsChangedEvent event) {
|
||||||
|
displayAddress.setUserData(null);
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
public void receiveTo(ReceiveToEvent event) {
|
public void receiveTo(ReceiveToEvent event) {
|
||||||
if(event.getReceiveEntry().getWallet().equals(getWalletForm().getWallet())) {
|
if(event.getReceiveEntry().getWallet().equals(getWalletForm().getWallet())) {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in a new issue