wallet encryption with argon2 key derivation

This commit is contained in:
Craig Raw
2020-05-18 15:50:46 +02:00
parent ae01fe9ee6
commit 68aeb5946a
11 changed files with 270 additions and 160 deletions
@@ -217,9 +217,10 @@ public class AppController implements Initializable {
WalletNameDialog dlg = new WalletNameDialog();
Optional<String> 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<String> 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<String> 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<Wallet> 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);
@@ -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();
}
}
@@ -43,7 +43,7 @@ public class WalletNameDialog extends Dialog<String> {
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<String> {
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");
@@ -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<String> 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"));
}
@@ -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);
}
}
@@ -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<ECKey> optionalPubKey = requestEncryption(walletForm.getEncryptionPubKey());
Optional<ECKey> 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<String> 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();
@@ -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();
}
}