diff --git a/drongo b/drongo index e20501d9..8ffd2250 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit e20501d95422bb4ef76002cb7a42c46b856143d9 +Subproject commit 8ffd22500754b77e420e2a3f887864e89a47e906 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index bfc13981..a0d5f618 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -217,9 +217,10 @@ public class AppController implements Initializable { WalletNameDialog dlg = new WalletNameDialog(); Optional walletName = dlg.showAndWait(); if(walletName.isPresent()) { - File walletFile = Storage.getStorage().getWalletFile(walletName.get()); + File walletFile = Storage.getWalletFile(walletName.get()); + Storage storage = new Storage(walletFile); Wallet wallet = new Wallet(walletName.get(), PolicyType.SINGLE, ScriptType.P2WPKH); - Tab tab = addWalletTab(walletFile, null, wallet); + Tab tab = addWalletTab(storage, wallet); tabs.getSelectionModel().select(tab); } } @@ -228,17 +229,17 @@ public class AppController implements Initializable { Stage window = new Stage(); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Open Wallet"); - fileChooser.setInitialDirectory(Storage.getStorage().getWalletsDir()); + fileChooser.setInitialDirectory(Storage.getWalletsDir()); File file = fileChooser.showOpenDialog(window); if(file != null) { try { Wallet wallet; String password = null; - ECKey encryptionPubKey = WalletForm.NO_PASSWORD_KEY; + Storage storage = new Storage(file); FileType fileType = IOUtils.getFileType(file); if(FileType.JSON.equals(fileType)) { - wallet = Storage.getStorage().loadWallet(file); + wallet = storage.loadWallet(); } else if(FileType.BINARY.equals(fileType)) { WalletPasswordDialog dlg = new WalletPasswordDialog(WalletPasswordDialog.PasswordRequirement.LOAD); Optional optionalPassword = dlg.showAndWait(); @@ -247,53 +248,12 @@ public class AppController implements Initializable { } password = optionalPassword.get(); - ECKey encryptionFullKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey(password); - wallet = Storage.getStorage().loadWallet(file, encryptionFullKey); - encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey); + wallet = storage.loadWallet(password); } else { throw new IOException("Unsupported file type"); } - if(wallet.containsSeeds()) { - //Derive xpub and master fingerprint from seed, potentially with passphrase - Wallet copy = wallet.copy(); - if(wallet.isEncrypted()) { - if(password == null) { - throw new IllegalStateException("Wallet seeds are encrypted but wallet is not"); - } - - copy.decrypt(password); - } - - for(Keystore copyKeystore : copy.getKeystores()) { - if(copyKeystore.hasSeed()) { - if(copyKeystore.getSeed().needsPassphrase()) { - KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(copyKeystore); - Optional optionalPassphrase = passphraseDialog.showAndWait(); - if(optionalPassphrase.isPresent()) { - copyKeystore.getSeed().setPassphrase(optionalPassphrase.get()); - } else { - return; - } - } else { - copyKeystore.getSeed().setPassphrase(""); - } - } - } - - for(int i = 0; i < wallet.getKeystores().size(); i++) { - Keystore keystore = wallet.getKeystores().get(i); - if(keystore.hasSeed()) { - Keystore copyKeystore = copy.getKeystores().get(i); - Keystore derivedKeystore = Keystore.fromSeed(copyKeystore.getSeed(), copyKeystore.getKeyDerivation().getDerivation()); - keystore.setKeyDerivation(derivedKeystore.getKeyDerivation()); - keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey()); - keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase()); - } - } - } - - Tab tab = addWalletTab(file, encryptionPubKey, wallet); + Tab tab = addWalletTab(storage, wallet); tabs.getSelectionModel().select(tab); } catch (InvalidPasswordException e) { showErrorDialog("Invalid Password", "The password was invalid."); @@ -308,8 +268,9 @@ public class AppController implements Initializable { Optional optionalWallet = dlg.showAndWait(); if(optionalWallet.isPresent()) { Wallet wallet = optionalWallet.get(); - File walletFile = Storage.getStorage().getWalletFile(wallet.getName()); - Tab tab = addWalletTab(walletFile, null, wallet); + File walletFile = Storage.getWalletFile(wallet.getName()); + Storage storage = new Storage(walletFile); + Tab tab = addWalletTab(storage, wallet); tabs.getSelectionModel().select(tab); } } @@ -327,21 +288,21 @@ public class AppController implements Initializable { } } - public Tab addWalletTab(File walletFile, ECKey encryptionPubKey, Wallet wallet) { + public Tab addWalletTab(Storage storage, Wallet wallet) { try { - String name = walletFile.getName(); + String name = storage.getWalletFile().getName(); if(name.endsWith(".json")) { name = name.substring(0, name.lastIndexOf('.')); } Tab tab = new Tab(name); - TabData tabData = new WalletTabData(TabData.TabType.WALLET, wallet, walletFile); + TabData tabData = new WalletTabData(TabData.TabType.WALLET, wallet, storage); tab.setUserData(tabData); tab.setContextMenu(getTabContextMenu(tab)); tab.setClosable(true); FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml")); tab.setContent(walletLoader.load()); WalletController controller = walletLoader.getController(); - WalletForm walletForm = new WalletForm(walletFile, encryptionPubKey, wallet); + WalletForm walletForm = new WalletForm(storage, wallet); controller.setWalletForm(walletForm); tabs.getTabs().add(tab); diff --git a/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java b/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java index 370bb554..abba5d3a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java +++ b/src/main/java/com/sparrowwallet/sparrow/WalletTabData.java @@ -3,17 +3,16 @@ package com.sparrowwallet.sparrow; import com.google.common.eventbus.Subscribe; import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.event.WalletChangedEvent; - -import java.io.File; +import com.sparrowwallet.sparrow.io.Storage; public class WalletTabData extends TabData { private Wallet wallet; - private final File walletFile; + private final Storage storage; - public WalletTabData(TabType type, Wallet wallet, File walletFile) { + public WalletTabData(TabType type, Wallet wallet, Storage storage) { super(type); this.wallet = wallet; - this.walletFile = walletFile; + this.storage = storage; EventManager.get().register(this); } @@ -22,13 +21,13 @@ public class WalletTabData extends TabData { return wallet; } - public File getWalletFile() { - return walletFile; + public Storage getStorage() { + return storage; } @Subscribe public void walletChanged(WalletChangedEvent event) { - if(event.getWalletFile().equals(walletFile)) { + if(event.getWalletFile().equals(storage.getWalletFile())) { wallet = event.getWallet(); } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java index 195de0a9..d78fe77e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletNameDialog.java @@ -43,7 +43,7 @@ public class WalletNameDialog extends Dialog { Platform.runLater( () -> { validationSupport.registerValidator(name, Validator.combine( Validator.createEmptyValidator("Wallet name is required"), - (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.getStorage().getWalletFile(newValue).exists()) + (Control c, String newValue) -> ValidationResult.fromErrorIf( c, "Wallet name is not unique", Storage.getWalletFile(newValue).exists()) )); validationSupport.setValidationDecorator(new StyleClassValidationDecoration()); }); @@ -52,7 +52,7 @@ public class WalletNameDialog extends Dialog { dialogPane.getButtonTypes().addAll(okButtonType); Button okButton = (Button) dialogPane.lookupButton(okButtonType); BooleanBinding isInvalid = Bindings.createBooleanBinding(() -> - name.getText().length() == 0 || Storage.getStorage().getWalletFile(name.getText()).exists(), name.textProperty()); + name.getText().length() == 0 || Storage.getWalletFile(name.getText()).exists(), name.textProperty()); okButton.disableProperty().bind(isInvalid); name.setPromptText("Wallet Name"); diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java index a8272439..addc6ed3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Storage.java @@ -3,33 +3,45 @@ package com.sparrowwallet.sparrow.io; import com.google.gson.*; import com.sparrowwallet.drongo.ExtendedKey; import com.sparrowwallet.drongo.Utils; -import com.sparrowwallet.drongo.crypto.ECKey; +import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.MnemonicException; import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.control.KeystorePassphraseDialog; import java.io.*; import java.lang.reflect.Type; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.Base64; +import java.util.Optional; import java.util.zip.*; +import static com.sparrowwallet.drongo.crypto.Argon2KeyDeriver.SPRW1_PARAMETERS; + public class Storage { + public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(ECKey.fromPrivate(Utils.hexToBytes("885e5a09708a167ea356a252387aa7c4893d138d632e296df8fbf5c12798bd28"))); + public static final String SPARROW_DIR = ".sparrow"; public static final String WALLETS_DIR = "wallets"; + public static final String HEADER_MAGIC_1 = "SPRW1"; + private static final int BINARY_HEADER_LENGTH = 28; - private static Storage SINGLETON; - + private File walletFile; private final Gson gson; + private AsymmetricKeyDeriver keyDeriver; + private ECKey encryptionPubKey; - private Storage() { - gson = getGson(); + public Storage(File walletFile) { + this.walletFile = walletFile; + this.gson = getGson(); + this.encryptionPubKey = NO_PASSWORD_KEY; } - public static Storage getStorage() { - if(SINGLETON == null) { - SINGLETON = new Storage(); - } - - return SINGLETON; + public File getWalletFile() { + return walletFile; } public static Gson getGson() { @@ -49,73 +61,200 @@ public class Storage { return gsonBuilder.setPrettyPrinting().disableHtmlEscaping().create(); } - public Wallet loadWallet(File file) throws IOException { - Reader reader = new FileReader(file); + public Wallet loadWallet() throws IOException, MnemonicException { + Reader reader = new FileReader(walletFile); Wallet wallet = gson.fromJson(reader, Wallet.class); reader.close(); + restorePublicKeysFromSeed(wallet, null); + return wallet; } - public Wallet loadWallet(File file, ECKey encryptionKey) throws IOException { - Reader reader = new InputStreamReader(new InflaterInputStream(new ECIESInputStream(new FileInputStream(file), encryptionKey, getEncryptionMagic())), StandardCharsets.UTF_8); + public Wallet loadWallet(String password) throws IOException, MnemonicException, StorageException { + InputStream fileStream = new FileInputStream(walletFile); + ECKey encryptionKey = getEncryptionKey(password, fileStream); + + InputStream inputStream = new InflaterInputStream(new ECIESInputStream(fileStream, encryptionKey, getEncryptionMagic())); + Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); Wallet wallet = gson.fromJson(reader, Wallet.class); reader.close(); + Key key = new Key(encryptionKey.getPrivKeyBytes(), keyDeriver.getSalt(), EncryptionType.Deriver.ARGON2); + restorePublicKeysFromSeed(wallet, key); + + encryptionPubKey = ECKey.fromPublicOnly(encryptionKey); return wallet; } - public void storeWallet(File file, Wallet wallet) throws IOException { - File parent = file.getParentFile(); + private void restorePublicKeysFromSeed(Wallet wallet, Key key) throws MnemonicException { + if(wallet.containsSeeds()) { + //Derive xpub and master fingerprint from seed, potentially with passphrase + Wallet copy = wallet.copy(); + if(wallet.isEncrypted()) { + if(key == null) { + throw new IllegalStateException("Wallet was not encrypted, but seed is"); + } + + copy.decrypt(key); + } + + for(Keystore copyKeystore : copy.getKeystores()) { + if(copyKeystore.hasSeed()) { + if(copyKeystore.getSeed().needsPassphrase()) { + KeystorePassphraseDialog passphraseDialog = new KeystorePassphraseDialog(copyKeystore); + Optional optionalPassphrase = passphraseDialog.showAndWait(); + if(optionalPassphrase.isPresent()) { + copyKeystore.getSeed().setPassphrase(optionalPassphrase.get()); + } else { + return; + } + } else { + copyKeystore.getSeed().setPassphrase(""); + } + } + } + + for(int i = 0; i < wallet.getKeystores().size(); i++) { + Keystore keystore = wallet.getKeystores().get(i); + if(keystore.hasSeed()) { + Keystore copyKeystore = copy.getKeystores().get(i); + Keystore derivedKeystore = Keystore.fromSeed(copyKeystore.getSeed(), copyKeystore.getKeyDerivation().getDerivation()); + keystore.setKeyDerivation(derivedKeystore.getKeyDerivation()); + keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey()); + keystore.getSeed().setPassphrase(copyKeystore.getSeed().getPassphrase()); + } + } + } + } + + public void storeWallet(Wallet wallet) throws IOException { + if(encryptionPubKey != null && !NO_PASSWORD_KEY.equals(encryptionPubKey)) { + storeWallet(encryptionPubKey, wallet); + return; + } + + File parent = walletFile.getParentFile(); if(!parent.exists() && !parent.mkdirs()) { throw new IOException("Could not create folder " + parent); } - if(!file.getName().endsWith(".json")) { - File jsonFile = new File(parent, file.getName() + ".json"); - if(file.exists()) { - if(!file.renameTo(jsonFile)) { - throw new IOException("Could not rename " + file.getName() + " to " + jsonFile.getName()); + if(!walletFile.getName().endsWith(".json")) { + File jsonFile = new File(parent, walletFile.getName() + ".json"); + if(walletFile.exists()) { + if(!walletFile.renameTo(jsonFile)) { + throw new IOException("Could not rename " + walletFile.getName() + " to " + jsonFile.getName()); } } - file = jsonFile; + walletFile = jsonFile; } - Writer writer = new FileWriter(file); + Writer writer = new FileWriter(walletFile); gson.toJson(wallet, writer); writer.close(); } - public void storeWallet(File file, ECKey encryptionKey, Wallet wallet) throws IOException { - File parent = file.getParentFile(); + private void storeWallet(ECKey encryptionPubKey, Wallet wallet) throws IOException { + File parent = walletFile.getParentFile(); if(!parent.exists() && !parent.mkdirs()) { throw new IOException("Could not create folder " + parent); } - if(file.getName().endsWith(".json")) { - File noJsonFile = new File(parent, file.getName().substring(0, file.getName().lastIndexOf('.'))); - if(file.exists()) { - if(!file.renameTo(noJsonFile)) { - throw new IOException("Could not rename " + file.getName() + " to " + noJsonFile.getName()); + if(walletFile.getName().endsWith(".json")) { + File noJsonFile = new File(parent, walletFile.getName().substring(0, walletFile.getName().lastIndexOf('.'))); + if(walletFile.exists()) { + if(!walletFile.renameTo(noJsonFile)) { + throw new IOException("Could not rename " + walletFile.getName() + " to " + noJsonFile.getName()); } } - file = noJsonFile; + walletFile = noJsonFile; } - OutputStreamWriter writer = new OutputStreamWriter(new DeflaterOutputStream(new ECIESOutputStream(new FileOutputStream(file), encryptionKey, getEncryptionMagic())), StandardCharsets.UTF_8); + OutputStream outputStream = new FileOutputStream(walletFile); + writeBinaryHeader(outputStream); + + OutputStreamWriter writer = new OutputStreamWriter(new DeflaterOutputStream(new ECIESOutputStream(outputStream, encryptionPubKey, getEncryptionMagic())), StandardCharsets.UTF_8); gson.toJson(wallet, writer); writer.close(); } + private void writeBinaryHeader(OutputStream outputStream) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(21); + buf.put(HEADER_MAGIC_1.getBytes(StandardCharsets.UTF_8)); + buf.put(keyDeriver.getSalt()); + + byte[] encoded = Base64.getEncoder().encode(buf.array()); + if(encoded.length != BINARY_HEADER_LENGTH) { + throw new IllegalStateException("Header length not " + BINARY_HEADER_LENGTH + " bytes"); + } + outputStream.write(encoded); + } + + public ECKey getEncryptionPubKey() { + return encryptionPubKey; + } + + public void setEncryptionPubKey(ECKey encryptionPubKey) { + this.encryptionPubKey = encryptionPubKey; + } + + public ECKey getEncryptionKey(String password) throws IOException, StorageException { + return getEncryptionKey(password, null); + } + + private ECKey getEncryptionKey(String password, InputStream inputStream) throws IOException, StorageException { + if(password.equals("")) { + return NO_PASSWORD_KEY; + } + + return getKeyDeriver(inputStream).deriveECKey(password); + } + + public AsymmetricKeyDeriver getKeyDeriver() { + return keyDeriver; + } + + void setKeyDeriver(AsymmetricKeyDeriver keyDeriver) { + this.keyDeriver = keyDeriver; + } + + private AsymmetricKeyDeriver getKeyDeriver(InputStream inputStream) throws IOException, StorageException { + if(keyDeriver == null) { + byte[] salt = new byte[SPRW1_PARAMETERS.saltLength]; + + if(inputStream != null) { + byte[] header = new byte[BINARY_HEADER_LENGTH]; + int read = inputStream.read(header); + if(read != BINARY_HEADER_LENGTH) { + throw new StorageException("Not a Sparrow wallet - invalid header"); + } + byte[] decodedHeader = Base64.getDecoder().decode(header); + byte[] magic = Arrays.copyOfRange(decodedHeader, 0, HEADER_MAGIC_1.length()); + if(!HEADER_MAGIC_1.equals(new String(magic, StandardCharsets.UTF_8))) { + throw new StorageException("Not a Sparrow wallet - invalid magic"); + } + salt = Arrays.copyOfRange(decodedHeader, HEADER_MAGIC_1.length(), decodedHeader.length); + } else { + SecureRandom secureRandom = new SecureRandom(); + secureRandom.nextBytes(salt); + } + + keyDeriver = new Argon2KeyDeriver(salt); + } + + return keyDeriver; + } + private static byte[] getEncryptionMagic() { return "BIE1".getBytes(StandardCharsets.UTF_8); } - public File getWalletFile(String walletName) { + public static File getWalletFile(String walletName) { + //TODO: Check for existing file return new File(getWalletsDir(), walletName); } - public File getWalletsDir() { + public static File getWalletsDir() { File walletsDir = new File(getSparrowDir(), WALLETS_DIR); if(!walletsDir.exists()) { walletsDir.mkdirs(); @@ -124,11 +263,11 @@ public class Storage { return walletsDir; } - private File getSparrowDir() { + private static File getSparrowDir() { return new File(getHomeDir(), SPARROW_DIR); } - private File getHomeDir() { + private static File getHomeDir() { return new File(System.getProperty("user.home")); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/StorageException.java b/src/main/java/com/sparrowwallet/sparrow/io/StorageException.java new file mode 100644 index 00000000..a2fca6fe --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/StorageException.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.sparrow.io; + +public class StorageException extends Exception { + public StorageException() { + super(); + } + + public StorageException(String message) { + super(message); + } + + public StorageException(Throwable cause) { + super(cause); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java index 5cff1f6b..0241c775 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SettingsController.java @@ -1,8 +1,7 @@ package com.sparrowwallet.sparrow.wallet; import com.google.common.eventbus.Subscribe; -import com.sparrowwallet.drongo.crypto.ECKey; -import com.sparrowwallet.drongo.crypto.Pbkdf2KeyDeriver; +import com.sparrowwallet.drongo.crypto.*; import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.PolicyType; import com.sparrowwallet.drongo.protocol.ScriptType; @@ -16,7 +15,7 @@ import com.sparrowwallet.sparrow.control.CopyableLabel; import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.event.SettingsChangedEvent; import com.sparrowwallet.sparrow.event.WalletChangedEvent; -import javafx.application.Platform; +import com.sparrowwallet.sparrow.io.Storage; import javafx.beans.property.SimpleIntegerProperty; import javafx.collections.FXCollections; import javafx.fxml.FXML; @@ -159,9 +158,9 @@ public class SettingsController extends WalletFormController implements Initiali apply.setOnAction(event -> { try { - Optional optionalPubKey = requestEncryption(walletForm.getEncryptionPubKey()); + Optional optionalPubKey = requestEncryption(walletForm.getStorage().getEncryptionPubKey()); if(optionalPubKey.isPresent()) { - walletForm.setEncryptionPubKey(optionalPubKey.get()); + walletForm.getStorage().setEncryptionPubKey(optionalPubKey.get()); walletForm.save(); revert.setDisable(true); apply.setDisable(true); @@ -260,7 +259,7 @@ public class SettingsController extends WalletFormController implements Initiali WalletPasswordDialog.PasswordRequirement requirement; if(existingPubKey == null) { requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_NEW; - } else if(WalletForm.NO_PASSWORD_KEY.equals(existingPubKey)) { + } else if(Storage.NO_PASSWORD_KEY.equals(existingPubKey)) { requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_EMPTY; } else { requirement = WalletPasswordDialog.PasswordRequirement.UPDATE_SET; @@ -270,19 +269,24 @@ public class SettingsController extends WalletFormController implements Initiali Optional password = dlg.showAndWait(); if(password.isPresent()) { if(password.get().isEmpty()) { - return Optional.of(WalletForm.NO_PASSWORD_KEY); + return Optional.of(Storage.NO_PASSWORD_KEY); } - ECKey encryptionFullKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey(password.get()); - ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey); + try { + ECKey encryptionFullKey = walletForm.getStorage().getEncryptionKey(password.get()); + ECKey encryptionPubKey = ECKey.fromPublicOnly(encryptionFullKey); - if(existingPubKey != null && !WalletForm.NO_PASSWORD_KEY.equals(existingPubKey) && !existingPubKey.equals(encryptionPubKey)) { - AppController.showErrorDialog("Incorrect Password", "The password was incorrect."); - return Optional.empty(); + if(existingPubKey != null && !Storage.NO_PASSWORD_KEY.equals(existingPubKey) && !existingPubKey.equals(encryptionPubKey)) { + AppController.showErrorDialog("Incorrect Password", "The password was incorrect."); + return Optional.empty(); + } + + Key key = new Key(encryptionFullKey.getPrivKeyBytes(), walletForm.getStorage().getKeyDeriver().getSalt(), EncryptionType.Deriver.ARGON2); + walletForm.getWallet().encrypt(key); + return Optional.of(encryptionPubKey); + } catch (Exception e) { + AppController.showErrorDialog("Wallet File Invalid", e.getMessage()); } - - walletForm.getWallet().encrypt(password.get()); - return Optional.of(encryptionPubKey); } return Optional.empty(); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java index 16da437f..30d1bb75 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletForm.java @@ -9,16 +9,12 @@ import java.io.File; import java.io.IOException; public class WalletForm { - public static final ECKey NO_PASSWORD_KEY = ECKey.fromPublicOnly(Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey("")); - - private final File walletFile; - private ECKey encryptionPubKey; + private final Storage storage; private Wallet oldWallet; private Wallet wallet; - public WalletForm(File walletFile, ECKey encryptionPubKey, Wallet currentWallet) { - this.walletFile = walletFile; - this.encryptionPubKey = encryptionPubKey; + public WalletForm(Storage storage, Wallet currentWallet) { + this.storage = storage; this.oldWallet = currentWallet; this.wallet = currentWallet.copy(); } @@ -27,16 +23,12 @@ public class WalletForm { return wallet; } + public Storage getStorage() { + return storage; + } + public File getWalletFile() { - return walletFile; - } - - public ECKey getEncryptionPubKey() { - return encryptionPubKey; - } - - public void setEncryptionPubKey(ECKey encryptionPubKey) { - this.encryptionPubKey = encryptionPubKey; + return storage.getWalletFile(); } public void revert() { @@ -44,12 +36,7 @@ public class WalletForm { } public void save() throws IOException { - if(encryptionPubKey.equals(NO_PASSWORD_KEY)) { - Storage.getStorage().storeWallet(walletFile, wallet); - } else { - Storage.getStorage().storeWallet(walletFile, encryptionPubKey, wallet); - } - + storage.storeWallet(wallet); oldWallet = wallet.copy(); } } diff --git a/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java b/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java index 1b3c0302..c5cb23d9 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java @@ -15,45 +15,46 @@ import java.io.*; public class StorageTest extends IoTest { @Test - public void loadWallet() throws IOException { - ECKey decryptionKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey("pass"); - Wallet wallet = Storage.getStorage().loadWallet(getFile("sparrow-single-wallet"), decryptionKey); + public void loadWallet() throws IOException, MnemonicException, StorageException { + Storage storage = new Storage(getFile("sparrow-single-wallet")); + Wallet wallet = storage.loadWallet("pass"); Assert.assertTrue(wallet.isValid()); } @Test - public void loadSeedWallet() throws IOException, MnemonicException { - ECKey decryptionKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey("pass"); - - Wallet wallet = Storage.getStorage().loadWallet(getFile("sparrow-single-seed-wallet"), decryptionKey); + public void loadSeedWallet() throws IOException, MnemonicException, StorageException { + Storage storage = new Storage(getFile("sparrow-single-seed-wallet")); + Wallet wallet = storage.loadWallet("pass"); Assert.assertTrue(wallet.isValid()); - Assert.assertEquals("testa1", wallet.getName()); + Assert.assertEquals("testd2", wallet.getName()); Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); Assert.assertEquals("pkh(60bcd3a7)", wallet.getDefaultPolicy().getMiniscript().getScript()); Assert.assertEquals("60bcd3a7", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); - Assert.assertEquals("m/84'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); - Assert.assertEquals("xpub6BrhGFTWPd3DQaGP7p5zTQkE5nqVbaRs23HNae8jAoNJYS2NGa9Sgpeqv1dS5ygwD4sQfwqLCk5qXRK45FTgnqHRcrPnts3Qgh78BZrnoMn", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); - Assert.assertEquals("a48767d6b58732a0cad17ed93e23022ec603a177e75461f2aed994713fbbe532b61f6c0758a8aedcf9b2b8102c01c6f3e3e212ca06f13644d4ac8dad66556e164b7eaf79d0b42eadecee8b735e97fc0a", Utils.bytesToHex(wallet.getKeystores().get(0).getSeed().getEncryptedData().getEncryptedBytes())); - Assert.assertNull(wallet.getKeystores().get(0).getSeed().getSeedBytes()); + Assert.assertEquals("m/84'/0'/3'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6BrhGFTWPd3DXo8s2BPxHHzCmBCyj8QvamcEUaq8EDwnwXpvvcU9LzpJqENHcqHkqwTn2vPhynGVoEqj3PAB3NxnYZrvCsSfoCniJKaggdy", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertEquals("af6ebd81714c301c3a71fe11a7a9c99ccef4b33d4b36582220767bfa92768a2aa040f88b015b2465f8075a8b9dbf892a7d6e6c49932109f2cbc05ba0bd7f355fbcc34c237f71be5fb4dd7f8184e44cb0", Utils.bytesToHex(wallet.getKeystores().get(0).getSeed().getEncryptedData().getEncryptedBytes())); + Assert.assertNull(wallet.getKeystores().get(0).getSeed().getMnemonicCode()); } @Test - public void saveWallet() throws IOException { - ECKey decryptionKey = Pbkdf2KeyDeriver.DEFAULT_INSTANCE.deriveECKey("pass"); - Wallet wallet = Storage.getStorage().loadWallet(getFile("sparrow-single-wallet"), decryptionKey); + public void saveWallet() throws IOException, MnemonicException, StorageException { + Storage storage = new Storage(getFile("sparrow-single-wallet")); + Wallet wallet = storage.loadWallet("pass"); Assert.assertTrue(wallet.isValid()); - ECKey encyptionKey = ECKey.fromPublicOnly(decryptionKey); File tempWallet = File.createTempFile("sparrow", "tmp"); tempWallet.deleteOnExit(); - ByteArrayOutputStream dummyFileOutputStream = new ByteArrayOutputStream(); - Storage.getStorage().storeWallet(tempWallet, encyptionKey, wallet); + Storage tempStorage = new Storage(tempWallet); + tempStorage.setKeyDeriver(storage.getKeyDeriver()); + tempStorage.setEncryptionPubKey(storage.getEncryptionPubKey()); + tempStorage.storeWallet(wallet); - wallet = Storage.getStorage().loadWallet(tempWallet, decryptionKey); + Storage temp2Storage = new Storage(tempWallet); + wallet = temp2Storage.loadWallet("pass"); Assert.assertTrue(wallet.isValid()); } } diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-seed-wallet b/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-seed-wallet index 44225ae5..2bad28b9 100644 --- a/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-seed-wallet +++ b/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-seed-wallet @@ -1 +1 @@ -QklFMQOXQWixo1F4kqJjn/7UeGDmbpNdMGPUP+vzRWdUE2vOcHfejUklKAzq1cTpK0mMEOOHCZ+yg0LxF29KMFQpoSpRTl6GRjnmpGIu3HPuu7bjbL6GvZQXFkvmK+9zsrllxZl6sYMjP4zoHJH6JgL4qGFpnM9n/mtOGYAuOw7zj4fm10teHLLgJLpkZ4ze0n/t8QhbyPPkeEoNmw/gt1PrwNDneKtALCNNdGopEq0QWD15OCEY7fSIfu0K1VO9oMZpOHs78p335Ka7bRRjuq+RWvZLz/X5hb9zVlFIu+KLW/preykMbfg4UiPcVHfc9wSLsmqZe9btYa3yxsem9xRW7J4gXMr7uqMTw/dlrK6XNg0wBXH6cuBed3M6Nu72Hz0OU+M64HEUnsGRYLgz3XcsZAU7+jeUQp6D8sVjH7GsPFIdZl0wzhWNHsgQGLoGXnWyvatsPsklW9BQ5U1d0PxeLxWppwj42Y7YA0+O3BrN7lUmD6xATNt9/xwVotPIllXT84r/OpzFbULzSBZ0uwwV+4tdCOa7FHPUed5gXZCPk6lhPHkz2N6Ehm3WGOQBQsoTfObefYUjHhB+a6Rpggm7QgkLhBEtgy7sPCV39fnRYM52uOwDUhNB8K/h/RadkMX51eXtbx23+LU+jM8zpi4T6yLM1Rq8H9YZ+rJXUIZPOHwX2ytnXkwHcYanThhxvQa55v3vbC5X/JFVSA+yik1CejXctjv9I++5mCCJtGpk/USAWzIb3nhKURPi/a3sg7y/n47s79AxvzvLndlY96UIaI1QVaNoaoJtt1+cBLzrXGlc4hChIqloZN4GJ4F1KabDuyXwagGvsd14zTH6ELAprmJkrd0l9JNTEBrJJbRqFEZj7CwrREyUkxGaiskca+ZyHm4LuwKo/6m5dKgiqyeMUB6WdZFXDERSp9ldf8n+o6OSxolEq4wWxM2uGdbHZTBsORq0JyIS4CQcfiC4UQoXJnQpGAfKWNsc1jS/x0BlV/9n2rhGxC0LRQZ2YbtLwfwd138= \ No newline at end of file +U1BSVzED7oX1HJm5jfGejCdFrag8QklFMQJ5dd4sdqdFohYxy0Vnk3jdhUqPPRf6iAC2PPNDo67JfH6VpVDKR8PC1kcKsIYnKE05gA1e+EfgPB/yd1z3xCu8UcwRqFdFimPJQaa28YOFKFeHdwlul+xI7U7wNKB2QZYGe2HZ/SaFO1ccMwHrZ85AxHhCI/6Qfk52heYE0qv3dbechg84J73qmx95Rl1FiMt4THZp8fKN827QyMz4ReC/uixhXerDQm6nKDb5KaNh5/aZSY8Qw8jIIqtveDkwZl1F7tA3H54Yxa4Ugz/Mcr4A3/rEaCrtMGS89XUDhaQU4GznKyK4DM4A/jw7CWnPHreSHZ79FL1/xfHDnNPAKpKDOfNqoGIL/QzgegwZLFQTQj5z81fFw370pAgh17h0diaJ+Z3TQR++/olPdNZYMU0Rl+xCjwABXUPB2AcdiIcUniGWKgXiUTW3B+nttb3m1WV/HCm/knQd42sGsFf5ZbjweqRzObvfMSkGaTKkB6ickw2VIxJSjiYTNWF3MVunUbFaqrk94OGnR10leg58iXzDvoX0zhkVdFZOxX4VjCKYCQQNng1pUj9MGpov1q7BXAiS1/ABkgvsD3tBZllV2Eg+P78UeuYpAjBNZnEjvydZOP8b86Yey12SnsUZCmnIeeOvDnqi603XdjNojQVltncIgAxlp3Bmj3tRSxUM49akj0hBsZASW3V1JC/P6cScpgv7e5/DyXIAFz7+yGzJ1cYMwUtVMOQMMJSc9y2nSMrEqFNJR+U8d9kfp8KYwst6GsJLUKBdDQMI/20O0PZSM0pWMJFE9iPK/YNDmjC7F0p8pw== \ No newline at end of file diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-wallet b/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-wallet index 74bf083d..a8c1ab24 100644 --- a/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-wallet +++ b/src/test/resources/com/sparrowwallet/sparrow/io/sparrow-single-wallet @@ -1 +1 @@ -QklFMQNI/quQo9N7RtbygK+yhlrMNxkSnXtlaC9Ia5trm7AufOtbKhGqrtv5bQ/YcRVVaj/eKhO7LWTGbC6EWFYbIle/tpTyQB5XdceCCWmbUDwyob+thVpMLLrVe9PQD+EH6GM2cWGFUZNMHdYM2N/EaLU4Z2nnDz9pLzg1jpOtU9n3D1IeivULxfkupsd0AqxkpkXJlc0y7udh2qzXk/BPffYkEN0NexspO2+I1+o81g1IcVRXNV7LR8o/woKRM4MPBhUNVOy2F5JyvKnsteBKpEpKa4AyHmhGRtIdyKIZK4+osIU9Ig+b/AItDj9OG354gpL7oiU65s7rF8UsJpDLtxIyONUL6becqsNNem0rTbHQ0PI1uoWHmQj8dUl8sqhdIwC13Hhnx0+M5ICrqs3gk5tkUyiCDA7684jrWLGRjUzUXRPmNJsWPqlnCD2+MY93dduMwbJqV1USrOZDXsMd9LuGAV+UqEDMuBRjwXDxXQldrIBp9QKYac1mKFvj9UOJr062T2gwGsSyKY2R6oCiGJPkOZjRoQ0HHwJukFYJgoRRI34Hnh49LUfJybv+VEfqz9VJZhWnDhCgcFZ9r1BwY4CZ \ No newline at end of file +U1BSVzEeO0vCCaJ0C+yv91xHOivxQklFMQM8Z+yJUyrrWWhzlBrAe2gLlAA7z7KlPMo7DYp5IRas2U0n7w9P2qz13wqmENZ2ilPmLwGrNpcFD8/re34I3oSudN5mjs+Z9xqx7xAHRrtXIiiLoKZS9p+v44AKQxBBrNgvj6seH2n2mJ+TUYVhp0s5IKzeFAPewfMzVKnmt7VWUzzug+uPUoyS44RapDqSM1u1GtkA4+fSGTdE8GF0mG4Y0KvKi5ZtLgaFxe/c/7bX4XlQdbnzzYpFxklUJQYkeWn9N3OmC8kJo97IGGGCUOd6bIwZtTj9KGG/whrgcID12yB5L7uvVMSpFLv6Qs27+La/L8UuPSpnO+gr5r4K1OtG9kxQhksp+8Ap0yzPOJzOMGLYXv7pR+edO5Q5PTQ4K6dN6zdYgsTY6HA36N6qgNQLM5RwzXh1WINp3pI/FnEKeffGIrDPAHueKDHUd/Zw6rhXDbDn28oK+zf8lR57Rx78JFF7JczB0jYJreIE0mPwN2fn0iB7JQwUGt6XM5hsvHK1n/JxO9OELXYyMaB/t4ihEdiw6HosncdhTwkKLDXb11wwj845vjN4Q8pGq/1YOuuEYRe0eiaGQFXTaw9QYWPO \ No newline at end of file