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

2
drongo

@ -1 +1 @@
Subproject commit e20501d95422bb4ef76002cb7a42c46b856143d9
Subproject commit 8ffd22500754b77e420e2a3f887864e89a47e906

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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");

View file

@ -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"));
}

View file

@ -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);
}
}

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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());
}
}

View file

@ -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=
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==

View file

@ -1 +1 @@
QklFMQNI/quQo9N7RtbygK+yhlrMNxkSnXtlaC9Ia5trm7AufOtbKhGqrtv5bQ/YcRVVaj/eKhO7LWTGbC6EWFYbIle/tpTyQB5XdceCCWmbUDwyob+thVpMLLrVe9PQD+EH6GM2cWGFUZNMHdYM2N/EaLU4Z2nnDz9pLzg1jpOtU9n3D1IeivULxfkupsd0AqxkpkXJlc0y7udh2qzXk/BPffYkEN0NexspO2+I1+o81g1IcVRXNV7LR8o/woKRM4MPBhUNVOy2F5JyvKnsteBKpEpKa4AyHmhGRtIdyKIZK4+osIU9Ig+b/AItDj9OG354gpL7oiU65s7rF8UsJpDLtxIyONUL6becqsNNem0rTbHQ0PI1uoWHmQj8dUl8sqhdIwC13Hhnx0+M5ICrqs3gk5tkUyiCDA7684jrWLGRjUzUXRPmNJsWPqlnCD2+MY93dduMwbJqV1USrOZDXsMd9LuGAV+UqEDMuBRjwXDxXQldrIBp9QKYac1mKFvj9UOJr062T2gwGsSyKY2R6oCiGJPkOZjRoQ0HHwJukFYJgoRRI34Hnh49LUfJybv+VEfqz9VJZhWnDhCgcFZ9r1BwY4CZ
U1BSVzEeO0vCCaJ0C+yv91xHOivxQklFMQM8Z+yJUyrrWWhzlBrAe2gLlAA7z7KlPMo7DYp5IRas2U0n7w9P2qz13wqmENZ2ilPmLwGrNpcFD8/re34I3oSudN5mjs+Z9xqx7xAHRrtXIiiLoKZS9p+v44AKQxBBrNgvj6seH2n2mJ+TUYVhp0s5IKzeFAPewfMzVKnmt7VWUzzug+uPUoyS44RapDqSM1u1GtkA4+fSGTdE8GF0mG4Y0KvKi5ZtLgaFxe/c/7bX4XlQdbnzzYpFxklUJQYkeWn9N3OmC8kJo97IGGGCUOd6bIwZtTj9KGG/whrgcID12yB5L7uvVMSpFLv6Qs27+La/L8UuPSpnO+gr5r4K1OtG9kxQhksp+8Ap0yzPOJzOMGLYXv7pR+edO5Q5PTQ4K6dN6zdYgsTY6HA36N6qgNQLM5RwzXh1WINp3pI/FnEKeffGIrDPAHueKDHUd/Zw6rhXDbDn28oK+zf8lR57Rx78JFF7JczB0jYJreIE0mPwN2fn0iB7JQwUGt6XM5hsvHK1n/JxO9OELXYyMaB/t4ihEdiw6HosncdhTwkKLDXb11wwj845vjN4Q8pGq/1YOuuEYRe0eiaGQFXTaw9QYWPO