From d68ab40c9457d9a0b6eade6a9630b1d4d46ee4eb Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 18 Apr 2024 16:04:06 +0200 Subject: [PATCH] add wallet import for samourai backup export --- drongo | 2 +- .../control/FileWalletKeystoreImportPane.java | 4 +- .../sparrow/control/WalletImportDialog.java | 6 +- .../sparrowwallet/sparrow/io/Samourai.java | 97 ++++++++++++++++++ src/main/resources/image/samourai.png | Bin 0 -> 2902 bytes src/main/resources/image/samourai@2x.png | Bin 0 -> 4926 bytes src/main/resources/image/samourai@3x.png | Bin 0 -> 9845 bytes 7 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/io/Samourai.java create mode 100644 src/main/resources/image/samourai.png create mode 100644 src/main/resources/image/samourai@2x.png create mode 100644 src/main/resources/image/samourai@3x.png 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 0000000000000000000000000000000000000000..2d4e2ad6fbb36633a384f28505ff6180b14b09af GIT binary patch literal 2902 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/image/samourai@3x.png b/src/main/resources/image/samourai@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f4a76ddbc84740d13f8c5eaa0eba64f5d147f5e1 GIT binary patch literal 9845 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