diff --git a/drongo b/drongo index 0df1f79e..f67a2caf 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 0df1f79e5c7e9fc1daa212c875c9da5dbcc0ee56 +Subproject commit f67a2caf5379a6931040f3a9b0c9203e9bfc3f44 diff --git a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java index 2897ee80..8077c55c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/DevicePane.java @@ -16,11 +16,8 @@ import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.*; -import com.sparrowwallet.sparrow.io.CardApi; -import com.sparrowwallet.sparrow.io.Device; -import com.sparrowwallet.sparrow.io.Hwi; +import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; -import com.sparrowwallet.sparrow.io.CardAuthorizationException; import com.sparrowwallet.sparrow.net.ElectrumServer; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; @@ -778,10 +775,12 @@ public class DevicePane extends TitledDescriptionPane { signButton.setDisable(false); } } else { - Hwi.SignPSBTService signPSBTService = new Hwi.SignPSBTService(device, passphrase.get(), psbt, OutputDescriptor.getOutputDescriptor(wallet), wallet.getFullName()); + Hwi.SignPSBTService signPSBTService = new Hwi.SignPSBTService(device, passphrase.get(), psbt, + OutputDescriptor.getOutputDescriptor(wallet), wallet.getFullName(), getDeviceRegistration()); signPSBTService.setOnSucceeded(workerStateEvent -> { PSBT signedPsbt = signPSBTService.getValue(); EventManager.get().post(new PSBTSignedEvent(psbt, signedPsbt)); + updateDeviceRegistrations(signPSBTService.getNewDeviceRegistrations()); }); signPSBTService.setOnFailed(workerStateEvent -> { setError("Signing Error", signPSBTService.getException().getMessage()); @@ -821,10 +820,11 @@ public class DevicePane extends TitledDescriptionPane { private void displayAddress() { Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(device, passphrase.get(), wallet.getScriptType(), outputDescriptor, - OutputDescriptor.getOutputDescriptor(wallet), wallet.getFullName()); + OutputDescriptor.getOutputDescriptor(wallet), wallet.getFullName(), getDeviceRegistration()); displayAddressService.setOnSucceeded(successEvent -> { String address = displayAddressService.getValue(); EventManager.get().post(new AddressDisplayedEvent(address)); + updateDeviceRegistrations(displayAddressService.getNewDeviceRegistrations()); }); displayAddressService.setOnFailed(failedEvent -> { setError("Could not display address", displayAddressService.getException().getMessage()); @@ -834,6 +834,26 @@ public class DevicePane extends TitledDescriptionPane { displayAddressService.start(); } + private byte[] getDeviceRegistration() { + Optional optKeystore = wallet.getKeystores().stream() + .filter(keystore -> keystore.getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint()) && keystore.getDeviceRegistration() != null).findFirst(); + return optKeystore.map(Keystore::getDeviceRegistration).orElse(null); + } + + private void updateDeviceRegistrations(Set newDeviceRegistrations) { + if(!newDeviceRegistrations.isEmpty()) { + List registrationKeystores = getDeviceRegistrationKeystores(); + if(!registrationKeystores.isEmpty()) { + registrationKeystores.forEach(keystore -> keystore.setDeviceRegistration(newDeviceRegistrations.iterator().next())); + EventManager.get().post(new KeystoreDeviceRegistrationsChangedEvent(wallet, registrationKeystores)); + } + } + } + + private List getDeviceRegistrationKeystores() { + return wallet.getKeystores().stream().filter(keystore -> keystore.getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint())).toList(); + } + private void signMessage() { if(device.isCard()) { try { diff --git a/src/main/java/com/sparrowwallet/sparrow/event/KeystoreDeviceRegistrationsChangedEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/KeystoreDeviceRegistrationsChangedEvent.java new file mode 100644 index 00000000..ae852e12 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/KeystoreDeviceRegistrationsChangedEvent.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.util.List; + +public class KeystoreDeviceRegistrationsChangedEvent extends WalletChangedEvent { + private final List changedKeystores; + + public KeystoreDeviceRegistrationsChangedEvent(Wallet wallet, List changedKeystores) { + super(wallet); + this.changedKeystores = changedKeystores; + } + + public List getChangedKeystores() { + return changedKeystores; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java index d74f5674..012b7064 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Hwi.java @@ -36,6 +36,8 @@ public class Hwi { private static boolean isPromptActive = false; + private final Set newDeviceRegistrations = new HashSet<>(); + static { //deleteHwiDir(); } @@ -161,7 +163,10 @@ public class Hwi { isPromptActive = true; Lark lark = getLark(passphrase, walletDescriptor, walletName, walletRegistration); - return lark.displayAddress(device.getType(), device.getPath(), addressDescriptor); + String address = lark.displayAddress(device.getType(), device.getPath(), addressDescriptor); + newDeviceRegistrations.addAll(lark.getWalletRegistrations().values()); + newDeviceRegistrations.remove(walletRegistration); + return address; } catch(DeviceException e) { throw new DisplayAddressException(e.getMessage(), e); } catch(RuntimeException e) { @@ -192,7 +197,10 @@ public class Hwi { try { isPromptActive = true; Lark lark = getLark(passphrase, walletDescriptor, walletName, walletRegistration); - return lark.signTransaction(device.getType(), device.getPath(), psbt); + PSBT signed = lark.signTransaction(device.getType(), device.getPath(), psbt); + newDeviceRegistrations.addAll(lark.getWalletRegistrations().values()); + newDeviceRegistrations.remove(walletRegistration); + return signed; } catch(DeviceException e) { throw new SignTransactionException(e.getMessage(), e); } catch(RuntimeException e) { @@ -346,16 +354,7 @@ public class Hwi { private final OutputDescriptor walletDescriptor; private final String walletName; private final byte[] walletRegistration; - - public DisplayAddressService(Device device, String passphrase, ScriptType scriptType, OutputDescriptor addressDescriptor, OutputDescriptor walletDescriptor, String walletName) { - this.device = device; - this.passphrase = passphrase; - this.scriptType = scriptType; - this.addressDescriptor = addressDescriptor; - this.walletDescriptor = walletDescriptor; - this.walletName = walletName; - this.walletRegistration = null; - } + private final Set newDeviceRegistrations = new HashSet<>(); public DisplayAddressService(Device device, String passphrase, ScriptType scriptType, OutputDescriptor addressDescriptor, OutputDescriptor walletDescriptor, String walletName, byte[] walletRegistration) { this.device = device; @@ -367,12 +366,18 @@ public class Hwi { this.walletRegistration = walletRegistration; } + public Set getNewDeviceRegistrations() { + return newDeviceRegistrations; + } + @Override protected Task createTask() { return new Task<>() { protected String call() throws DisplayAddressException { Hwi hwi = new Hwi(); - return hwi.displayAddress(device, passphrase, scriptType, addressDescriptor, walletDescriptor, walletName, walletRegistration); + String address = hwi.displayAddress(device, passphrase, scriptType, addressDescriptor, walletDescriptor, walletName, walletRegistration); + newDeviceRegistrations.addAll(hwi.newDeviceRegistrations); + return address; } }; } @@ -453,15 +458,7 @@ public class Hwi { private final OutputDescriptor walletDescriptor; private final String walletName; private final byte[] walletRegistration; - - public SignPSBTService(Device device, String passphrase, PSBT psbt, OutputDescriptor walletDescriptor, String walletName) { - this.device = device; - this.passphrase = passphrase; - this.psbt = psbt; - this.walletDescriptor = walletDescriptor; - this.walletName = walletName; - this.walletRegistration = null; - } + private final Set newDeviceRegistrations = new HashSet<>(); public SignPSBTService(Device device, String passphrase, PSBT psbt, OutputDescriptor walletDescriptor, String walletName, byte[] walletRegistration) { this.device = device; @@ -472,12 +469,18 @@ public class Hwi { this.walletRegistration = walletRegistration; } + public Set getNewDeviceRegistrations() { + return newDeviceRegistrations; + } + @Override protected Task createTask() { return new Task<>() { protected PSBT call() throws SignTransactionException { Hwi hwi = new Hwi(); - return hwi.signPSBT(device, passphrase, psbt, walletDescriptor, walletName, walletRegistration); + PSBT signed = hwi.signPSBT(device, passphrase, psbt, walletDescriptor, walletName, walletRegistration); + newDeviceRegistrations.addAll(hwi.newDeviceRegistrations); + return signed; } }; } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java index 8eeab03b..6eb42151 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/DbPersistence.java @@ -355,6 +355,13 @@ public class DbPersistence implements Persistence { } } + if(!dirtyPersistables.registrationKeystores.isEmpty()) { + KeystoreDao keystoreDao = handle.attach(KeystoreDao.class); + for(Keystore keystore : dirtyPersistables.registrationKeystores) { + keystoreDao.updateDeviceRegistration(keystore.getDeviceRegistration(), keystore.getId()); + } + } + dirtyPersistablesMap.remove(wallet); } finally { walletDao.setSchema(DEFAULT_SCHEMA); @@ -792,6 +799,13 @@ public class DbPersistence implements Persistence { } } + @Subscribe + public void keystoreDeviceRegistrationsChanged(KeystoreDeviceRegistrationsChangedEvent event) { + if(persistsFor(event.getWallet())) { + updateExecutor.execute(() -> dirtyPersistablesMap.computeIfAbsent(event.getWallet(), key -> new DirtyPersistables()).registrationKeystores.addAll(event.getChangedKeystores())); + } + } + @Subscribe public void walletWatchLastChanged(WalletWatchLastChangedEvent event) { if(persistsFor(event.getWallet())) { @@ -815,6 +829,7 @@ public class DbPersistence implements Persistence { public final Map removedUtxoMixes = new HashMap<>(); public final List labelKeystores = new ArrayList<>(); public final List encryptionKeystores = new ArrayList<>(); + public final List registrationKeystores = new ArrayList<>(); public String toString() { return "Dirty Persistables" + @@ -834,7 +849,8 @@ public class DbPersistence implements Persistence { "\nUTXO mixes changed:" + changedUtxoMixes + "\nUTXO mixes removed:" + removedUtxoMixes + "\nKeystore labels:" + labelKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()) + - "\nKeystore encryptions:" + encryptionKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()); + "\nKeystore encryptions:" + encryptionKeystores.stream().map(Keystore::getLabel).collect(Collectors.toList()) + + "\nKeystore registrations:" + registrationKeystores.stream().map(Keystore::getDeviceRegistration).collect(Collectors.toList()); } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java index 27366863..a887e5c2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreDao.java @@ -13,16 +13,16 @@ import org.jdbi.v3.sqlobject.statement.SqlUpdate; import java.util.List; public interface KeystoreDao { - @SqlQuery("select keystore.id, keystore.label, keystore.source, keystore.walletModel, keystore.masterFingerprint, keystore.derivationPath, keystore.extendedPublicKey, keystore.externalPaymentCode, " + + @SqlQuery("select keystore.id, keystore.label, keystore.source, keystore.walletModel, keystore.masterFingerprint, keystore.derivationPath, keystore.extendedPublicKey, keystore.externalPaymentCode, keystore.deviceRegistration, " + "masterPrivateExtendedKey.id, masterPrivateExtendedKey.privateKey, masterPrivateExtendedKey.chainCode, masterPrivateExtendedKey.initialisationVector, masterPrivateExtendedKey.encryptedBytes, masterPrivateExtendedKey.keySalt, masterPrivateExtendedKey.deriver, masterPrivateExtendedKey.crypter, " + "seed.id, seed.type, seed.mnemonicString, seed.initialisationVector, seed.encryptedBytes, seed.keySalt, seed.deriver, seed.crypter, seed.needsPassphrase, seed.creationTimeSeconds " + "from keystore left join masterPrivateExtendedKey on keystore.masterPrivateExtendedKey = masterPrivateExtendedKey.id left join seed on keystore.seed = seed.id where keystore.wallet = ? order by keystore.index asc") @RegisterRowMapper(KeystoreMapper.class) List getForWalletId(Long id); - @SqlUpdate("insert into keystore (label, source, walletModel, masterFingerprint, derivationPath, extendedPublicKey, externalPaymentCode, masterPrivateExtendedKey, seed, wallet, index) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + @SqlUpdate("insert into keystore (label, source, walletModel, masterFingerprint, derivationPath, extendedPublicKey, externalPaymentCode, deviceRegistration, masterPrivateExtendedKey, seed, wallet, index) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") @GetGeneratedKeys("id") - long insert(String label, int source, int walletModel, String masterFingerprint, String derivationPath, String extendedPublicKey, String externalPaymentCode, Long masterPrivateExtendedKey, Long seed, long wallet, int index); + long insert(String label, int source, int walletModel, String masterFingerprint, String derivationPath, String extendedPublicKey, String externalPaymentCode, byte[] deviceRegistration, Long masterPrivateExtendedKey, Long seed, long wallet, int index); @SqlUpdate("insert into masterPrivateExtendedKey (privateKey, chainCode, initialisationVector, encryptedBytes, keySalt, deriver, crypter, creationTimeSeconds) values (?, ?, ?, ?, ?, ?, ?, ?)") @GetGeneratedKeys("id") @@ -41,6 +41,9 @@ public interface KeystoreDao { @SqlUpdate("update keystore set label = ? where id = ?") void updateLabel(String label, long id); + @SqlUpdate("update keystore set deviceRegistration = ? where id = ?") + void updateDeviceRegistration(byte[] deviceRegistration, long id); + default void addKeystores(Wallet wallet) { for(int i = 0; i < wallet.getKeystores().size(); i++) { Keystore keystore = wallet.getKeystores().get(i); @@ -73,6 +76,7 @@ public interface KeystoreDao { keystore.getKeyDerivation().getDerivationPath(), keystore.hasMasterPrivateKey() || wallet.isBip47() ? null : keystore.getExtendedPublicKey().toString(), keystore.getExternalPaymentCode() == null ? null : keystore.getExternalPaymentCode().toString(), + keystore.getDeviceRegistration(), keystore.getMasterPrivateExtendedKey() == null ? null : keystore.getMasterPrivateExtendedKey().getId(), keystore.getSeed() == null ? null : keystore.getSeed().getId(), wallet.getId(), i); keystore.setId(id); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java index 977c6f61..4a019b9c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/db/KeystoreMapper.java @@ -25,6 +25,7 @@ public class KeystoreMapper implements RowMapper { keystore.setKeyDerivation(new KeyDerivation(rs.getString("keystore.masterFingerprint"), rs.getString("keystore.derivationPath"))); keystore.setExtendedPublicKey(rs.getString("keystore.extendedPublicKey") == null ? null : ExtendedKey.fromDescriptor(rs.getString("keystore.extendedPublicKey"))); keystore.setExternalPaymentCode(rs.getString("keystore.externalPaymentCode") == null ? null : PaymentCode.fromString(rs.getString("keystore.externalPaymentCode"))); + keystore.setDeviceRegistration(rs.getBytes("keystore.deviceRegistration")); if(rs.getBytes("masterPrivateExtendedKey.privateKey") != null) { MasterPrivateExtendedKey masterPrivateExtendedKey = new MasterPrivateExtendedKey(rs.getBytes("masterPrivateExtendedKey.privateKey"), rs.getBytes("masterPrivateExtendedKey.chainCode")); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java index 9ec6fe89..93e32829 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/ReceiveController.java @@ -10,6 +10,7 @@ import com.google.zxing.qrcode.QRCodeWriter; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.wallet.BlockTransactionHashIndex; +import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.KeystoreSource; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.AppServices; @@ -234,7 +235,10 @@ public class ReceiveController extends WalletFormController implements Initializ } else { Device actualDevice = possibleDevices.get(0); Hwi.DisplayAddressService displayAddressService = new Hwi.DisplayAddressService(actualDevice, "", wallet.getScriptType(), addressDescriptor, - OutputDescriptor.getOutputDescriptor(walletForm.getWallet()), walletForm.getWallet().getFullName()); + OutputDescriptor.getOutputDescriptor(walletForm.getWallet()), walletForm.getWallet().getFullName(), getDeviceRegistration(actualDevice)); + displayAddressService.setOnSucceeded(successEvent -> { + updateDeviceRegistrations(actualDevice, displayAddressService.getNewDeviceRegistrations()); + }); displayAddressService.setOnFailed(failedEvent -> { Platform.runLater(() -> { DeviceDisplayAddressDialog dlg = new DeviceDisplayAddressDialog(wallet, addressDescriptor); @@ -252,6 +256,26 @@ public class ReceiveController extends WalletFormController implements Initializ } } + private byte[] getDeviceRegistration(Device device) { + Optional optKeystore = getWalletForm().getWallet().getKeystores().stream() + .filter(keystore -> keystore.getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint()) && keystore.getDeviceRegistration() != null).findFirst(); + return optKeystore.map(Keystore::getDeviceRegistration).orElse(null); + } + + private void updateDeviceRegistrations(Device device, Set newDeviceRegistrations) { + if(!newDeviceRegistrations.isEmpty()) { + List registrationKeystores = getDeviceRegistrationKeystores(device); + if(!registrationKeystores.isEmpty()) { + registrationKeystores.forEach(keystore -> keystore.setDeviceRegistration(newDeviceRegistrations.iterator().next())); + EventManager.get().post(new KeystoreDeviceRegistrationsChangedEvent(getWalletForm().getWallet(), registrationKeystores)); + } + } + } + + private List getDeviceRegistrationKeystores(Device device) { + return getWalletForm().getWallet().getKeystores().stream().filter(keystore -> keystore.getKeyDerivation().getMasterFingerprint().equals(device.getFingerprint())).toList(); + } + public void clear() { if(currentEntry != null) { label.textProperty().unbindBidirectional(currentEntry.labelProperty()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index ff4cc38f..7940724b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -640,6 +640,13 @@ public class WalletForm { } } + @Subscribe + public void keystoreDeviceRegistrationsChanged(KeystoreDeviceRegistrationsChangedEvent event) { + if(event.getWallet() == wallet) { + Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet))); + } + } + @Subscribe public void walletTabsClosed(WalletTabsClosedEvent event) { for(WalletTabData tabData : event.getClosedWalletTabData()) { diff --git a/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql b/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql new file mode 100644 index 00000000..b896178c --- /dev/null +++ b/src/main/resources/com/sparrowwallet/sparrow/sql/V9__KeystoreRegistration.sql @@ -0,0 +1 @@ +alter table keystore add column deviceRegistration varbinary(32) after externalPaymentCode; \ No newline at end of file