diff --git a/drongo b/drongo index 3f4ee7af..7584bcf2 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 3f4ee7af747b80976cda8ebd3c687b6a4ba5ea3f +Subproject commit 7584bcf26001d3705bb9467349112bc701905e7f diff --git a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java index 471b79e2..e715d39a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/FileWalletKeystoreImportPane.java @@ -38,6 +38,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane { private final KeystoreFileImport importer; private String fileName; private byte[] fileBytes; + private String password; public FileWalletKeystoreImportPane(KeystoreFileImport importer) { super(importer, importer.getName(), "Wallet import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable(), importer.isFileFormatAvailable()); @@ -46,6 +47,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane { protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException { this.fileName = fileName; + this.password = password; List scriptTypes = ScriptType.getAddressableScriptTypes(PolicyType.SINGLE); if(wallets != null && !wallets.isEmpty()) { @@ -83,7 +85,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane { EventManager.get().post(new WalletImportEvent(wallet)); } else { ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes); - Keystore keystore = importer.getKeystore(scriptType, bais, ""); + Keystore keystore = importer.getKeystore(scriptType, bais, password); Wallet wallet = new Wallet(); wallet.setName(Files.getNameWithoutExtension(fileName)); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java index 29683b28..93a3f9ae 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WalletImportDialog.java @@ -51,7 +51,8 @@ public class WalletImportDialog extends Dialog { AnchorPane.setRightAnchor(scrollPane, 0.0); importAccordion = new Accordion(); - List keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(), new GordianSeedTool(), new SeedSigner(), new SpecterDIY(), new Krux(), new AirGapVault()); + List keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(), + new GordianSeedTool(), new SeedSigner(), new SpecterDIY(), new Krux(), new AirGapVault(), new Samourai()); for(KeystoreFileImport importer : keystoreImporters) { if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) { FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer); @@ -59,7 +60,8 @@ public class WalletImportDialog extends Dialog { } } - List walletImporters = new ArrayList<>(List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow(), new JadeMultisig())); + List walletImporters = new ArrayList<>(List.of(new Bip129(), new CaravanMultisig(), new ColdcardMultisig(), new CoboVaultMultisig(), new Electrum(), + new KeystoneMultisig(), new Descriptor(), new SpecterDesktop(), new BlueWalletMultisig(), new Sparrow(), new JadeMultisig())); if(!selectedWalletForms.isEmpty()) { walletImporters.add(new WalletLabels(selectedWalletForms)); } diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Samourai.java b/src/main/java/com/sparrowwallet/sparrow/io/Samourai.java new file mode 100644 index 00000000..7181eb08 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/Samourai.java @@ -0,0 +1,97 @@ +package com.sparrowwallet.sparrow.io; + +import com.google.common.io.CharStreams; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; +import com.samourai.wallet.crypto.AESUtil; +import com.samourai.wallet.util.CharSequenceX; +import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.*; + +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@SuppressWarnings("deprecation") +public class Samourai implements KeystoreFileImport { + @Override + public String getKeystoreImportDescription(int account) { + return "Import the wallet backup file samourai.txt exported from the Samourai app."; + } + + @Override + public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { + try { + String input = CharStreams.toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + Gson gson = new Gson(); + Type stringStringMap = new TypeToken>() { + }.getType(); + Map map = gson.fromJson(input, stringStringMap); + + String payload = input; + if(map.containsKey("payload")) { + payload = map.get("payload").getAsString(); + } + + int version = 1; + if(map.containsKey("version")) { + version = map.get("version").getAsInt(); + } + + String decrypted; + if(version == 1) { + decrypted = AESUtil.decrypt(payload, new CharSequenceX(password), AESUtil.DefaultPBKDF2Iterations); + } else if(version == 2) { + decrypted = AESUtil.decryptSHA256(payload, new CharSequenceX(password)); + } else { + throw new ImportException("Unsupported backup version: " + version); + } + + SamouraiBackup backup = gson.fromJson(decrypted, SamouraiBackup.class); + DeterministicSeed seed = new DeterministicSeed(Utils.hexToBytes(backup.wallet.seed), password, 0); + Keystore keystore = Keystore.fromSeed(seed, scriptType.getDefaultDerivation()); + keystore.setLabel(getWalletModel().toDisplayString()); + return keystore; + } catch(ImportException e) { + throw e; + } catch(Exception e) { + throw new ImportException("Error importing backup", e); + } + } + + @Override + public boolean isKeystoreImportScannable() { + return false; + } + + @Override + public boolean isEncrypted(File file) { + return true; + } + + @Override + public String getName() { + return "Samourai Backup"; + } + + @Override + public WalletModel getWalletModel() { + return WalletModel.SAMOURAI; + } + + private static class SamouraiBackup { + public SamouraiWallet wallet; + } + + private static class SamouraiWallet { + public boolean testnet; + public String seed; + public String fingerprint; + } +} diff --git a/src/main/resources/image/samourai.png b/src/main/resources/image/samourai.png new file mode 100644 index 00000000..2d4e2ad6 Binary files /dev/null and b/src/main/resources/image/samourai.png differ diff --git a/src/main/resources/image/samourai@2x.png b/src/main/resources/image/samourai@2x.png new file mode 100644 index 00000000..235351ba Binary files /dev/null and b/src/main/resources/image/samourai@2x.png differ diff --git a/src/main/resources/image/samourai@3x.png b/src/main/resources/image/samourai@3x.png new file mode 100644 index 00000000..f4a76ddb Binary files /dev/null and b/src/main/resources/image/samourai@3x.png differ