mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2024-12-25 05:06:45 +00:00
add tapsigner message signing support
This commit is contained in:
parent
4fb8c5a61b
commit
f938506a3f
7 changed files with 113 additions and 32 deletions
2
drongo
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit e2a4c32db317b9e950cfbec822cc8103332d29ff
|
||||
Subproject commit b487396417fbdf3c73c24399a778855c97a26584
|
|
@ -94,6 +94,10 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
|
||||
initialise(device);
|
||||
|
||||
messageProperty.addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> setDescription(newValue));
|
||||
});
|
||||
|
||||
buttonBox.getChildren().addAll(setPassphraseButton, importButton);
|
||||
}
|
||||
|
||||
|
@ -167,6 +171,10 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
|
||||
initialise(device);
|
||||
|
||||
messageProperty.addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> setDescription(newValue));
|
||||
});
|
||||
|
||||
buttonBox.getChildren().addAll(setPassphraseButton, signMessageButton);
|
||||
}
|
||||
|
||||
|
@ -581,6 +589,9 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
}
|
||||
|
||||
Service<Keystore> importService = new CardImportPane.CardImportService(importer, pin.get(), derivation);
|
||||
importService.messageProperty().addListener((observable, oldValue, newValue) -> {
|
||||
messageProperty.set(newValue);
|
||||
});
|
||||
handleCardOperation(importService, importButton, "Import", event -> {
|
||||
importKeystore(derivation, importService.getValue());
|
||||
});
|
||||
|
@ -725,17 +736,32 @@ public class DevicePane extends TitledDescriptionPane {
|
|||
}
|
||||
|
||||
private void signMessage() {
|
||||
Hwi.SignMessageService signMessageService = new Hwi.SignMessageService(device, passphrase.get(), message, keyDerivation.getDerivationPath());
|
||||
signMessageService.setOnSucceeded(successEvent -> {
|
||||
String signature = signMessageService.getValue();
|
||||
EventManager.get().post(new MessageSignedEvent(wallet, signature));
|
||||
});
|
||||
signMessageService.setOnFailed(failedEvent -> {
|
||||
setError("Could not sign message", signMessageService.getException().getMessage());
|
||||
signMessageButton.setDisable(false);
|
||||
});
|
||||
setDescription("Signing message...");
|
||||
signMessageService.start();
|
||||
if(device.isCard()) {
|
||||
try {
|
||||
CardApi cardApi = new CardApi(pin.get());
|
||||
Service<String> signMessageService = cardApi.getSignMessageService(message, wallet.getScriptType(), keyDerivation.getDerivation(), messageProperty);
|
||||
handleCardOperation(signMessageService, signMessageButton, "Signing", event -> {
|
||||
String signature = signMessageService.getValue();
|
||||
EventManager.get().post(new MessageSignedEvent(wallet, signature));
|
||||
});
|
||||
} catch(Exception e) {
|
||||
log.error("Signing Error: " + e.getMessage(), e);
|
||||
setError("Signing Error", e.getMessage());
|
||||
signButton.setDisable(false);
|
||||
}
|
||||
} else {
|
||||
Hwi.SignMessageService signMessageService = new Hwi.SignMessageService(device, passphrase.get(), message, keyDerivation.getDerivationPath());
|
||||
signMessageService.setOnSucceeded(successEvent -> {
|
||||
String signature = signMessageService.getValue();
|
||||
EventManager.get().post(new MessageSignedEvent(wallet, signature));
|
||||
});
|
||||
signMessageService.setOnFailed(failedEvent -> {
|
||||
setError("Could not sign message", signMessageService.getException().getMessage());
|
||||
signMessageButton.setDisable(false);
|
||||
});
|
||||
setDescription("Signing message...");
|
||||
signMessageService.start();
|
||||
}
|
||||
}
|
||||
|
||||
private void discoverKeystores() {
|
||||
|
|
|
@ -353,7 +353,7 @@ public class EntryCell extends TreeTableCell<Entry, Entry> implements Confirmati
|
|||
private static boolean canSignMessage(WalletNode walletNode) {
|
||||
Wallet wallet = walletNode.getWallet();
|
||||
return wallet.getKeystores().size() == 1 && wallet.getScriptType() != ScriptType.P2TR &&
|
||||
(wallet.getKeystores().get(0).hasPrivateKey() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB) &&
|
||||
(wallet.getKeystores().get(0).hasPrivateKey() || wallet.getKeystores().get(0).getSource() == KeystoreSource.HW_USB || wallet.getKeystores().get(0).getWalletModel().isCard()) &&
|
||||
(!wallet.isBip47() || walletNode.getKeyPurpose() == KeyPurpose.RECEIVE);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,7 @@ import com.sparrowwallet.drongo.address.InvalidAddressException;
|
|||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
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.WalletNode;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.OpenWalletsEvent;
|
||||
|
@ -269,8 +266,8 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
if(wallet.getKeystores().size() != 1) {
|
||||
throw new IllegalArgumentException("Cannot sign messages using a wallet with multiple keystores - a single key is required");
|
||||
}
|
||||
if(!wallet.getKeystores().get(0).hasPrivateKey() && wallet.getKeystores().get(0).getSource() != KeystoreSource.HW_USB) {
|
||||
throw new IllegalArgumentException("Cannot sign messages using a wallet without private keys or a USB keystore");
|
||||
if(!wallet.getKeystores().get(0).hasPrivateKey() && wallet.getKeystores().get(0).getSource() != KeystoreSource.HW_USB && !wallet.getKeystores().get(0).getWalletModel().isCard()) {
|
||||
throw new IllegalArgumentException("Cannot sign messages using a wallet without private keys or a connected keystore");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,8 +316,8 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
} else {
|
||||
signUnencryptedKeystore(signingWallet);
|
||||
}
|
||||
} else if(signingWallet.containsSource(KeystoreSource.HW_USB)) {
|
||||
signUsbKeystore(signingWallet);
|
||||
} else if(signingWallet.containsSource(KeystoreSource.HW_USB) || wallet.getKeystores().get(0).getWalletModel().isCard()) {
|
||||
signDeviceKeystore(signingWallet);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,10 +336,10 @@ public class MessageSignDialog extends Dialog<ButtonBar.ButtonData> {
|
|||
}
|
||||
}
|
||||
|
||||
private void signUsbKeystore(Wallet usbWallet) {
|
||||
List<String> fingerprints = List.of(usbWallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
KeyDerivation fullDerivation = usbWallet.getKeystores().get(0).getKeyDerivation().extend(walletNode.getDerivation());
|
||||
DeviceSignMessageDialog deviceSignMessageDialog = new DeviceSignMessageDialog(fingerprints, usbWallet, message.getText().trim(), fullDerivation);
|
||||
private void signDeviceKeystore(Wallet deviceWallet) {
|
||||
List<String> fingerprints = List.of(deviceWallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint());
|
||||
KeyDerivation fullDerivation = deviceWallet.getKeystores().get(0).getKeyDerivation().extend(walletNode.getDerivation());
|
||||
DeviceSignMessageDialog deviceSignMessageDialog = new DeviceSignMessageDialog(fingerprints, deviceWallet, message.getText().trim(), fullDerivation);
|
||||
Optional<String> optSignature = deviceSignMessageDialog.showAndWait();
|
||||
if(optSignature.isPresent()) {
|
||||
signature.clear();
|
||||
|
|
|
@ -5,10 +5,7 @@ import com.sparrowwallet.drongo.KeyDerivation;
|
|||
import com.sparrowwallet.drongo.Utils;
|
||||
import com.sparrowwallet.drongo.crypto.ChildNumber;
|
||||
import com.sparrowwallet.drongo.crypto.ECKey;
|
||||
import com.sparrowwallet.drongo.protocol.Base58;
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.protocol.SigHash;
|
||||
import com.sparrowwallet.drongo.protocol.TransactionSignature;
|
||||
import com.sparrowwallet.drongo.protocol.*;
|
||||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTInput;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTInputSigner;
|
||||
|
@ -186,6 +183,40 @@ public class CardApi {
|
|||
}
|
||||
}
|
||||
|
||||
public Service<String> getSignMessageService(String message, ScriptType scriptType, List<ChildNumber> derivation, StringProperty messageProperty) {
|
||||
return new SignMessageService(message, scriptType, derivation, messageProperty);
|
||||
}
|
||||
|
||||
String signMessage(String message, ScriptType scriptType, List<ChildNumber> derivation) throws CardException {
|
||||
List<ChildNumber> keystoreDerivation = derivation.subList(0, derivation.size() - 2);
|
||||
List<ChildNumber> subPathDerivation = derivation.subList(derivation.size() - 2, derivation.size());
|
||||
|
||||
Keystore cardKeystore = getKeystore();
|
||||
KeyDerivation cardKeyDerivation = cardKeystore.getKeyDerivation();
|
||||
Keystore signingKeystore = cardKeystore;
|
||||
try {
|
||||
if(!cardKeyDerivation.getDerivation().equals(keystoreDerivation)) {
|
||||
setDerivation(keystoreDerivation);
|
||||
signingKeystore = getKeystore();
|
||||
}
|
||||
|
||||
WalletNode addressNode = new WalletNode(KeyDerivation.writePath(subPathDerivation));
|
||||
ECKey addressPubKey = signingKeystore.getPubKey(addressNode);
|
||||
return addressPubKey.signMessage(message, scriptType, hash -> {
|
||||
try {
|
||||
CardSign cardSign = cardProtocol.sign(cvc, subPathDerivation, hash);
|
||||
return cardSign.getSignature();
|
||||
} catch(CardException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
if(signingKeystore != cardKeystore) {
|
||||
setDerivation(cardKeystore.getKeyDerivation().getDerivation());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
try {
|
||||
cardProtocol.disconnect();
|
||||
|
@ -289,4 +320,31 @@ public class CardApi {
|
|||
return pubkey;
|
||||
}
|
||||
}
|
||||
|
||||
public class SignMessageService extends Service<String> {
|
||||
private final String message;
|
||||
private final ScriptType scriptType;
|
||||
private final List<ChildNumber> derivation;
|
||||
private final StringProperty messageProperty;
|
||||
|
||||
public SignMessageService(String message, ScriptType scriptType, List<ChildNumber> derivation, StringProperty messageProperty) {
|
||||
this.message = message;
|
||||
this.scriptType = scriptType;
|
||||
this.derivation = derivation;
|
||||
this.messageProperty = messageProperty;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<String> createTask() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected String call() throws Exception {
|
||||
CardStatus cardStatus = getStatus();
|
||||
checkWait(cardStatus, new SimpleIntegerProperty(), messageProperty);
|
||||
|
||||
return signMessage(message, scriptType, derivation);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -754,7 +754,7 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
Optional<Keystore> softwareKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.SW_SEED)).findAny();
|
||||
Optional<Keystore> usbKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB) || keystore.getSource().equals(KeystoreSource.SW_WATCH)).findAny();
|
||||
Optional<Keystore> bip47Keystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getSource().equals(KeystoreSource.SW_PAYMENT_CODE)).findAny();
|
||||
Optional<Keystore> cardKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getWalletModel().equals(WalletModel.TAPSIGNER)).findAny();
|
||||
Optional<Keystore> cardKeystore = signingWallet.getKeystores().stream().filter(keystore -> keystore.getWalletModel().isCard()).findAny();
|
||||
if(softwareKeystore.isEmpty() && usbKeystore.isEmpty() && bip47Keystore.isEmpty() && cardKeystore.isEmpty()) {
|
||||
signButton.setDisable(true);
|
||||
} else if(softwareKeystore.isEmpty() && bip47Keystore.isEmpty() && usbKeystore.isEmpty()) {
|
||||
|
@ -995,7 +995,7 @@ public class HeadersController extends TransactionFormController implements Init
|
|||
List<String> fingerprints = headersForm.getSigningWallet().getKeystores().stream().map(keystore -> keystore.getKeyDerivation().getMasterFingerprint()).collect(Collectors.toList());
|
||||
List<Device> signingDevices = AppServices.getDevices().stream().filter(device -> fingerprints.contains(device.getFingerprint())).collect(Collectors.toList());
|
||||
if(signingDevices.isEmpty() &&
|
||||
(headersForm.getSigningWallet().getKeystores().stream().noneMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB) || keystore.getSource().equals(KeystoreSource.SW_WATCH) || keystore.getWalletModel().equals(WalletModel.TAPSIGNER)) ||
|
||||
(headersForm.getSigningWallet().getKeystores().stream().noneMatch(keystore -> keystore.getSource().equals(KeystoreSource.HW_USB) || keystore.getSource().equals(KeystoreSource.SW_WATCH) || keystore.getWalletModel().isCard()) ||
|
||||
(headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getSource().equals(KeystoreSource.SW_SEED)) && headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getSource().equals(KeystoreSource.SW_WATCH))))) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -282,7 +282,7 @@ public class KeystoreController extends WalletFormController implements Initiali
|
|||
type.setGraphic(getTypeIcon(keystore));
|
||||
viewSeedButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasSeed());
|
||||
viewKeyButton.setVisible(keystore.getSource() == KeystoreSource.SW_SEED && keystore.hasMasterPrivateExtendedKey());
|
||||
changePinButton.setVisible(keystore.getWalletModel() == WalletModel.TAPSIGNER);
|
||||
changePinButton.setVisible(keystore.getWalletModel().isCard());
|
||||
|
||||
importButton.setText(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import..." : "Replace...");
|
||||
importButton.setTooltip(new Tooltip(keystore.getSource() == KeystoreSource.SW_WATCH ? "Import a keystore from an external source" : "Replace this keystore with another source"));
|
||||
|
|
Loading…
Reference in a new issue