add wallet import for samourai backup export

This commit is contained in:
Craig Raw 2024-04-18 16:04:06 +02:00
parent 31346e2afa
commit d68ab40c94
7 changed files with 105 additions and 4 deletions

2
drongo

@ -1 +1 @@
Subproject commit 3f4ee7af747b80976cda8ebd3c687b6a4ba5ea3f Subproject commit 7584bcf26001d3705bb9467349112bc701905e7f

View file

@ -38,6 +38,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane {
private final KeystoreFileImport importer; private final KeystoreFileImport importer;
private String fileName; private String fileName;
private byte[] fileBytes; private byte[] fileBytes;
private String password;
public FileWalletKeystoreImportPane(KeystoreFileImport importer) { public FileWalletKeystoreImportPane(KeystoreFileImport importer) {
super(importer, importer.getName(), "Wallet import", importer.getKeystoreImportDescription(), "image/" + importer.getWalletModel().getType() + ".png", importer.isKeystoreImportScannable(), importer.isFileFormatAvailable()); 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 { protected void importFile(String fileName, InputStream inputStream, String password) throws ImportException {
this.fileName = fileName; this.fileName = fileName;
this.password = password;
List<ScriptType> scriptTypes = ScriptType.getAddressableScriptTypes(PolicyType.SINGLE); List<ScriptType> scriptTypes = ScriptType.getAddressableScriptTypes(PolicyType.SINGLE);
if(wallets != null && !wallets.isEmpty()) { if(wallets != null && !wallets.isEmpty()) {
@ -83,7 +85,7 @@ public class FileWalletKeystoreImportPane extends FileImportPane {
EventManager.get().post(new WalletImportEvent(wallet)); EventManager.get().post(new WalletImportEvent(wallet));
} else { } else {
ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes); ByteArrayInputStream bais = new ByteArrayInputStream(fileBytes);
Keystore keystore = importer.getKeystore(scriptType, bais, ""); Keystore keystore = importer.getKeystore(scriptType, bais, password);
Wallet wallet = new Wallet(); Wallet wallet = new Wallet();
wallet.setName(Files.getNameWithoutExtension(fileName)); wallet.setName(Files.getNameWithoutExtension(fileName));

View file

@ -51,7 +51,8 @@ public class WalletImportDialog extends Dialog<Wallet> {
AnchorPane.setRightAnchor(scrollPane, 0.0); AnchorPane.setRightAnchor(scrollPane, 0.0);
importAccordion = new Accordion(); importAccordion = new Accordion();
List<KeystoreFileImport> keystoreImporters = List.of(new ColdcardSinglesig(), new CoboVaultSinglesig(), new Jade(), new KeystoneSinglesig(), new PassportSinglesig(), new GordianSeedTool(), new SeedSigner(), new SpecterDIY(), new Krux(), new AirGapVault()); List<KeystoreFileImport> 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) { for(KeystoreFileImport importer : keystoreImporters) {
if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) { if(!importer.isDeprecated() || Config.get().isShowDeprecatedImportExport()) {
FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer); FileWalletKeystoreImportPane importPane = new FileWalletKeystoreImportPane(importer);
@ -59,7 +60,8 @@ public class WalletImportDialog extends Dialog<Wallet> {
} }
} }
List<WalletImport> 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<WalletImport> 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()) { if(!selectedWalletForms.isEmpty()) {
walletImporters.add(new WalletLabels(selectedWalletForms)); walletImporters.add(new WalletLabels(selectedWalletForms));
} }

View file

@ -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<Map<String, JsonElement>>() {
}.getType();
Map<String, JsonElement> 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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB