From 6d202f1522c361d7b01b86749b7d6363f76629ef Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Sat, 25 Apr 2020 11:39:29 +0200 Subject: [PATCH] wallet import and export - coldcard and electrum --- drongo | 2 +- .../sparrowwallet/sparrow/AppController.java | 2 +- .../sparrow/external/ColdcardMultisig.java | 186 ++++++++++++++++++ .../sparrow/external/ColdcardSinglesig.java | 81 ++++++++ .../sparrow/external/Electrum.java | 164 +++++++++++++++ .../sparrow/external/Export.java | 5 + .../sparrow/external/ExportException.java | 19 ++ .../sparrow/external/Import.java | 5 + .../sparrow/external/ImportException.java | 19 ++ .../sparrow/external/KeystoreImport.java | 13 ++ .../external/MultisigWalletImport.java | 10 + .../external/SinglesigWalletImport.java | 11 ++ .../sparrow/external/WalletExport.java | 10 + .../sparrow/storage/Storage.java | 16 +- .../external/ColdcardMultisigTest.java | 120 +++++++++++ .../external/ColdcardSinglesigTest.java | 25 +++ .../sparrow/external/ElectrumTest.java | 86 ++++++++ .../sparrow/external/ImportExportTest.java | 10 + .../sparrow/external/cc-multisig-export-1.txt | 11 ++ .../sparrow/external/cc-multisig-export-2.txt | 11 ++ .../cc-multisig-export-multideriv.txt | 14 ++ .../external/cc-multisig-keystore-1.json | 9 + .../external/cc-multisig-keystore-2.json | 9 + .../sparrow/external/cc-wallet-dump.txt | 97 +++++++++ .../external/electrum-multisig-wallet.json | 2 + .../external/electrum-singlesig-wallet.json | 25 +++ 26 files changed, 948 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/Electrum.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/Export.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/ExportException.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/Import.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/ImportException.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/MultisigWalletImport.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/external/WalletExport.java create mode 100644 src/test/java/com/sparrowwallet/sparrow/external/ColdcardMultisigTest.java create mode 100644 src/test/java/com/sparrowwallet/sparrow/external/ColdcardSinglesigTest.java create mode 100644 src/test/java/com/sparrowwallet/sparrow/external/ElectrumTest.java create mode 100644 src/test/java/com/sparrowwallet/sparrow/external/ImportExportTest.java create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-1.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-2.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-multideriv.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-1.json create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-2.json create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/cc-wallet-dump.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/electrum-multisig-wallet.json create mode 100644 src/test/resources/com/sparrowwallet/sparrow/external/electrum-singlesig-wallet.json diff --git a/drongo b/drongo index 282628e4..294649de 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit 282628e4558b04dfa17c3f85247378204f8c82ff +Subproject commit 294649de669497283934933487d09e1dae9f3996 diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 760163d8..c79ab6d8 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -210,7 +210,7 @@ public class AppController implements Initializable { Optional walletName = dlg.showAndWait(); if(walletName.isPresent()) { File walletFile = Storage.getStorage().getWalletFile(walletName.get()); - Wallet wallet = new Wallet(PolicyType.SINGLE, ScriptType.P2WPKH); + Wallet wallet = new Wallet(walletName.get(), PolicyType.SINGLE, ScriptType.P2WPKH); Tab tab = addWalletTab(walletFile, null, wallet); tabs.getSelectionModel().select(tab); } diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java new file mode 100644 index 00000000..247dcffe --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardMultisig.java @@ -0,0 +1,186 @@ +package com.sparrowwallet.sparrow.external; + +import com.google.common.io.CharStreams; +import com.google.gson.Gson; +import com.sparrowwallet.drongo.ExtendedPublicKey; +import com.sparrowwallet.drongo.KeyDerivation; +import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.policy.Policy; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import com.sparrowwallet.sparrow.storage.Storage; + +import java.io.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class ColdcardMultisig implements MultisigWalletImport, KeystoreImport, WalletExport { + private final Gson gson = new Gson(); + + @Override + public String getName() { + return "Coldcard (Multisig)"; + } + + @Override + public PolicyType getPolicyType() { + return PolicyType.MULTI; + } + + @Override + public Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException { + InputStreamReader reader = new InputStreamReader(inputStream); + ColdcardKeystore cck = Storage.getStorage().getGson().fromJson(reader, ColdcardKeystore.class); + + Keystore keystore = new Keystore("Coldcard " + cck.xfp); + + if(scriptType.equals(ScriptType.P2SH)) { + keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv)); + keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(cck.p2sh)); + } else if(scriptType.equals(ScriptType.P2SH_P2WSH)) { + keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_p2sh_deriv)); + keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(cck.p2wsh_p2sh)); + } else if(scriptType.equals(ScriptType.P2WSH)) { + keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2wsh_deriv)); + keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(cck.p2wsh)); + } else { + throw new ImportException("Correct derivation not found for script type: " + scriptType); + } + + return keystore; + } + + public static class ColdcardKeystore { + public String p2sh_deriv; + public String p2sh; + public String p2wsh_p2sh_deriv; + public String p2wsh_p2sh; + public String p2wsh_deriv; + public String p2wsh; + public String xfp; + } + + @Override + public String getKeystoreImportDescription() { + return "Import file created by using the Settings > Multisig Wallets > Export XPUB feature on your Coldcard"; + } + + @Override + public Wallet importWallet(InputStream inputStream) throws ImportException { + Wallet wallet = new Wallet(); + wallet.setPolicyType(PolicyType.MULTI); + + int threshold = 2; + ScriptType scriptType = null; + String derivation = null; + + try { + List lines = CharStreams.readLines(new InputStreamReader(inputStream)); + for (String line : lines) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + String[] keyValue = line.split(":"); + if (keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + switch (key) { + case "Name": + wallet.setName(value.trim()); + break; + case "Policy": + threshold = Integer.parseInt(value.split(" ")[0]); + break; + case "Derivation": + case "# derivation": + derivation = value; + break; + case "Format": + scriptType = ScriptType.valueOf(value.replace("P2WSH-P2SH", "P2SH_P2WSH")); + break; + default: + if (key.length() == 8 && Utils.isHex(key)) { + Keystore keystore = new Keystore("Coldcard " + key); + keystore.setKeyDerivation(new KeyDerivation(key, derivation)); + keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(value)); + wallet.getKeystores().add(keystore); + } + } + } + } + + + Policy policy = Policy.getPolicy(PolicyType.MULTI, scriptType, wallet.getKeystores(), threshold); + wallet.setDefaultPolicy(policy); + wallet.setScriptType(scriptType); + + return wallet; + } catch(Exception e) { + throw new ImportException(e); + } + } + + @Override + public String getWalletImportDescription() { + return "Import file created by using the Settings > Multisig Wallets > [Wallet Detail] > Coldcard Export feature on your Coldcard"; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + if(!wallet.isValid()) { + throw new ExportException("Cannot export an incomplete wallet"); + } + + if(!wallet.getPolicyType().equals(PolicyType.MULTI)) { + throw new ExportException("Coldcard multisig import requires a multisig wallet"); + } + + boolean multipleDerivations = false; + Set derivationSet = new HashSet<>(); + for(Keystore keystore : wallet.getKeystores()) { + derivationSet.add(keystore.getKeyDerivation().getDerivationPath()); + } + if(derivationSet.size() > 1) { + multipleDerivations = true; + } + + try { + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + writer.append("# Coldcard Multisig setup file (created by Sparrow)\n"); + writer.append("#\n"); + writer.append("Name: ").append(wallet.getName()).append("\n"); + writer.append("Policy: ").append(Integer.toString(wallet.getDefaultPolicy().getNumSignaturesRequired())).append(" of ").append(Integer.toString(wallet.getKeystores().size())).append("\n"); + if(!multipleDerivations) { + writer.append("Derivation: ").append(wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()).append("\n"); + } + writer.append("Format: ").append(wallet.getScriptType().toString().replace("P2SH-P2WSH", "P2WSH-P2SH")).append("\n"); + writer.append("\n"); + + for(Keystore keystore : wallet.getKeystores()) { + if(multipleDerivations) { + writer.append("# derivation: ").append(keystore.getKeyDerivation().getDerivationPath()).append("\n"); + } + writer.append(keystore.getKeyDerivation().getMasterFingerprint().toUpperCase()).append(": ").append(keystore.getExtendedPublicKey().toString()).append("\n"); + if(multipleDerivations) { + writer.append("\n"); + } + } + + writer.flush(); + writer.close(); + } catch(Exception e) { + throw new ExportException(e); + } + } + + @Override + public String getWalletExportDescription() { + return "Export file that can be read by your Coldcard using the Settings > Multisig Wallets > Import from SD feature"; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java new file mode 100644 index 00000000..9b41fbf9 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/ColdcardSinglesig.java @@ -0,0 +1,81 @@ +package com.sparrowwallet.sparrow.external; + +import com.google.common.io.CharStreams; +import com.sparrowwallet.drongo.ExtendedPublicKey; +import com.sparrowwallet.drongo.KeyDerivation; +import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.policy.Policy; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.List; + +import static com.sparrowwallet.drongo.protocol.ScriptType.*; + +public class ColdcardSinglesig implements SinglesigWalletImport { + public static final List ALLOWED_SCRIPT_TYPES = List.of(P2PKH, P2SH_P2WPKH, P2WPKH); + + @Override + public String getName() { + return "Coldcard"; + } + + @Override + public Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException { + if(!ALLOWED_SCRIPT_TYPES.contains(scriptType)) { + throw new ImportException("Script type of " + scriptType + " is not allowed"); + } + + Wallet wallet = new Wallet(); + wallet.setPolicyType(PolicyType.SINGLE); + wallet.setScriptType(scriptType); + String masterFingerprint = null; + + try { + List lines = CharStreams.readLines(new InputStreamReader(inputStream)); + + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) { + continue; + } + + if(line.startsWith("xpub")) { + ExtendedPublicKey masterXpub = ExtendedPublicKey.fromDescriptor(line); + masterFingerprint = Utils.bytesToHex(masterXpub.getPubKey().getFingerprint()).toUpperCase(); + wallet.setName("Coldcard " + masterFingerprint); + continue; + } + + String[] keyValue = line.split("=>"); + if(keyValue.length == 2) { + String key = keyValue[0].trim(); + String value = keyValue[1].trim(); + + if(!key.equals("m") && scriptType.getDefaultDerivation().startsWith(key)) { + ExtendedPublicKey extPubKey = ExtendedPublicKey.fromDescriptor(value); + Keystore keystore = new Keystore(); + keystore.setKeyDerivation(new KeyDerivation(masterFingerprint, key)); + keystore.setExtendedPublicKey(extPubKey); + wallet.getKeystores().add(keystore); + break; + } + } + } + + wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), 1)); + return wallet; + } catch(Exception e) { + throw new ImportException(e); + } + } + + @Override + public String getWalletImportDescription() { + return "Import file created by using the Advanced > Dump Summary feature on your Coldcard"; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java new file mode 100644 index 00000000..0abd2c33 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/Electrum.java @@ -0,0 +1,164 @@ +package com.sparrowwallet.sparrow.external; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import com.sparrowwallet.drongo.ExtendedPublicKey; +import com.sparrowwallet.drongo.KeyDerivation; +import com.sparrowwallet.drongo.Utils; +import com.sparrowwallet.drongo.policy.Policy; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.io.*; +import java.lang.reflect.Type; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.Map; + +public class Electrum implements SinglesigWalletImport, MultisigWalletImport, WalletExport { + @Override + public String getName() { + return "Electrum"; + } + + @Override + public Wallet importWallet(InputStream inputStream) throws ImportException { + InputStreamReader reader = new InputStreamReader(inputStream); + try { + Gson gson = new Gson(); + Type stringStringMap = new TypeToken>(){}.getType(); + Map map = gson.fromJson(reader, stringStringMap); + + ElectrumJsonWallet ew = new ElectrumJsonWallet(); + ew.wallet_type = map.get("wallet_type").getAsString(); + + for(String key : map.keySet()) { + if(key.startsWith("x") || key.equals("keystore")) { + ElectrumKeystore ek = gson.fromJson(map.get(key), ElectrumKeystore.class); + if(ek.root_fingerprint == null && ek.ckcc_xfp != null) { + byte[] le = new byte[4]; + Utils.uint32ToByteArrayLE(Long.parseLong(ek.ckcc_xfp), le, 0); + ek.root_fingerprint = Utils.bytesToHex(le).toUpperCase(); + } + ew.keystores.put(key, ek); + } + } + + Wallet wallet = new Wallet(); + ScriptType scriptType = null; + + for(ElectrumKeystore ek : ew.keystores.values()) { + Keystore keystore = new Keystore(ek.label); + keystore.setKeyDerivation(new KeyDerivation(ek.root_fingerprint, ek.derivation)); + keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(ek.xpub)); + wallet.getKeystores().add(keystore); + + ExtendedPublicKey.XpubHeader xpubHeader = ExtendedPublicKey.XpubHeader.fromXpub(ek.xpub); + scriptType = xpubHeader.getDefaultScriptType(); + } + + wallet.setScriptType(scriptType); + + if(ew.wallet_type.equals("standard")) { + wallet.setPolicyType(PolicyType.SINGLE); + wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, scriptType, wallet.getKeystores(), 1)); + } else if(ew.wallet_type.contains("of")) { + wallet.setPolicyType(PolicyType.MULTI); + String[] mOfn = ew.wallet_type.split("of"); + int threshold = Integer.parseInt(mOfn[0]); + wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.MULTI, scriptType, wallet.getKeystores(), threshold)); + } else { + throw new ImportException("Unknown Electrum wallet type of " + ew.wallet_type); + } + + if(!wallet.isValid()) { + throw new IllegalStateException("Electrum wallet is in an inconsistent state"); + } + + return wallet; + } catch (Exception e) { + throw new ImportException(e); + } + } + + @Override + public String getWalletImportDescription() { + return "Import an Electrum wallet"; + } + + @Override + public Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException { + Wallet wallet = importWallet(inputStream); + wallet.setScriptType(scriptType); + + return wallet; + } + + @Override + public void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException { + try { + ElectrumJsonWallet ew = new ElectrumJsonWallet(); + if(wallet.getPolicyType().equals(PolicyType.SINGLE)) { + ew.wallet_type = "standard"; + } else if(wallet.getPolicyType().equals(PolicyType.MULTI)) { + ew.wallet_type = wallet.getDefaultPolicy().getNumSignaturesRequired() + "of" + wallet.getKeystores().size(); + } else { + throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy"); + } + + ExtendedPublicKey.XpubHeader xpubHeader = ExtendedPublicKey.XpubHeader.fromScriptType(wallet.getScriptType()); + + int index = 1; + for(Keystore keystore : wallet.getKeystores()) { + ElectrumKeystore ek = new ElectrumKeystore(); + ek.xpub = keystore.getExtendedPublicKey().toString(xpubHeader); + ek.derivation = keystore.getKeyDerivation().getDerivationPath(); + ek.root_fingerprint = keystore.getKeyDerivation().getMasterFingerprint(); + ek.label = keystore.getLabel(); + + if(wallet.getPolicyType().equals(PolicyType.SINGLE)) { + ew.keystores.put("keystore", ek); + } else if(wallet.getPolicyType().equals(PolicyType.MULTI)) { + ew.keystores.put("x" + index + "/", ek); + } + + index++; + } + + Gson gson = new Gson(); + JsonObject eJson = gson.toJsonTree(ew.keystores).getAsJsonObject(); + eJson.addProperty("wallet_type", ew.wallet_type); + + gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + String json = gson.toJson(eJson); + outputStream.write(json.getBytes(StandardCharsets.UTF_8)); + outputStream.flush(); + outputStream.close(); + } catch (Exception e) { + throw new ExportException(e); + } + } + + @Override + public String getWalletExportDescription() { + return "Export this wallet as an Electrum wallet file"; + } + + private static class ElectrumJsonWallet { + public Map keystores = new LinkedHashMap<>(); + public String wallet_type; + } + + public static class ElectrumKeystore { + public String xpub; + public String hw_type; + public String ckcc_xfp; + public String root_fingerprint; + public String label; + public String soft_device_id; + public String type; + public String derivation; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/Export.java b/src/main/java/com/sparrowwallet/sparrow/external/Export.java new file mode 100644 index 00000000..87522d7f --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/Export.java @@ -0,0 +1,5 @@ +package com.sparrowwallet.sparrow.external; + +public interface Export { + String getName(); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ExportException.java b/src/main/java/com/sparrowwallet/sparrow/external/ExportException.java new file mode 100644 index 00000000..4cb427c4 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/ExportException.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.sparrow.external; + +public class ExportException extends Throwable { + public ExportException() { + super(); + } + + public ExportException(String message) { + super(message); + } + + public ExportException(Throwable cause) { + super(cause); + } + + public ExportException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/Import.java b/src/main/java/com/sparrowwallet/sparrow/external/Import.java new file mode 100644 index 00000000..98cde97c --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/Import.java @@ -0,0 +1,5 @@ +package com.sparrowwallet.sparrow.external; + +public interface Import { + String getName(); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/ImportException.java b/src/main/java/com/sparrowwallet/sparrow/external/ImportException.java new file mode 100644 index 00000000..7290f679 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/ImportException.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.sparrow.external; + +public class ImportException extends Exception { + public ImportException() { + super(); + } + + public ImportException(String message) { + super(message); + } + + public ImportException(Throwable cause) { + super(cause); + } + + public ImportException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java new file mode 100644 index 00000000..fe27e113 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/KeystoreImport.java @@ -0,0 +1,13 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; + +import java.io.InputStream; + +public interface KeystoreImport extends Import { + PolicyType getPolicyType(); + Keystore getKeystore(ScriptType scriptType, InputStream inputStream) throws ImportException; + String getKeystoreImportDescription(); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/MultisigWalletImport.java b/src/main/java/com/sparrowwallet/sparrow/external/MultisigWalletImport.java new file mode 100644 index 00000000..1fe2d24b --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/MultisigWalletImport.java @@ -0,0 +1,10 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.io.InputStream; + +public interface MultisigWalletImport extends Import { + String getWalletImportDescription(); + Wallet importWallet(InputStream inputStream) throws ImportException; +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java b/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java new file mode 100644 index 00000000..5aeadf55 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/SinglesigWalletImport.java @@ -0,0 +1,11 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.io.InputStream; + +public interface SinglesigWalletImport extends Import { + String getWalletImportDescription(); + Wallet importWallet(InputStream inputStream, ScriptType scriptType) throws ImportException; +} diff --git a/src/main/java/com/sparrowwallet/sparrow/external/WalletExport.java b/src/main/java/com/sparrowwallet/sparrow/external/WalletExport.java new file mode 100644 index 00000000..1e92a87f --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/external/WalletExport.java @@ -0,0 +1,10 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.wallet.Wallet; + +import java.io.OutputStream; + +public interface WalletExport extends Export { + void exportWallet(Wallet wallet, OutputStream outputStream) throws ExportException; + String getWalletExportDescription(); +} diff --git a/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java b/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java index 1564d067..57fa7102 100644 --- a/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java +++ b/src/main/java/com/sparrowwallet/sparrow/storage/Storage.java @@ -36,6 +36,10 @@ public class Storage { return SINGLETON; } + public Gson getGson() { + return gson; + } + public Wallet loadWallet(File file) throws IOException { Reader reader = new FileReader(file); Wallet wallet = gson.fromJson(reader, Wallet.class); @@ -44,18 +48,6 @@ public class Storage { return wallet; } - public static final void main(String[] args) throws Exception { - File file = new File("/Users/scy/.electrum-latest/wallets/scyone"); - ECKey pubKey = ECKey.createKeyPbkdf2HmacSha512("***REMOVED***"); - - BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); - byte[] encrypted = ByteStreams.toByteArray(inputStream); - byte[] decrypted = pubKey.decryptEcies(encrypted, getEncryptionMagic()); - String jsonWallet = inflate(decrypted); - - System.out.println(jsonWallet); - } - public Wallet loadWallet(File file, ECKey encryptionKey) throws IOException { BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file)); byte[] encrypted = ByteStreams.toByteArray(inputStream); diff --git a/src/test/java/com/sparrowwallet/sparrow/external/ColdcardMultisigTest.java b/src/test/java/com/sparrowwallet/sparrow/external/ColdcardMultisigTest.java new file mode 100644 index 00000000..504e134b --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/external/ColdcardMultisigTest.java @@ -0,0 +1,120 @@ +package com.sparrowwallet.sparrow.external; + +import com.google.common.io.ByteStreams; +import com.sparrowwallet.drongo.ExtendedPublicKey; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.Assert; +import org.junit.Test; + +import java.io.*; + +public class ColdcardMultisigTest extends ImportExportTest { + @Test + public void importKeystore1() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH_P2WSH, getInputStream("cc-multisig-keystore-1.json")); + Assert.assertEquals("Coldcard 0F056943", keystore.getLabel()); + Assert.assertEquals("m/48'/1'/0'/1'", keystore.getKeyDerivation().getDerivationPath()); + Assert.assertEquals("0f056943", keystore.getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals(ExtendedPublicKey.fromDescriptor("Upub5T4XUooQzDXL58NCHk8ZCw9BsRSLCtnyHeZEExAq1XdnBFXiXVrHFuvvmh3TnCR7XmKHxkwqdACv68z7QKT1vwru9L1SZSsw8B2fuBvtSa6"), keystore.getExtendedPublicKey()); + Assert.assertTrue(keystore.isValid()); + } + + @Test(expected = ImportException.class) + public void importKeystore1IncorrectScriptType() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH_P2WPKH, getInputStream("cc-multisig-keystore-1.json")); + } + + @Test + public void importKeystore2() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH, getInputStream("cc-multisig-keystore-2.json")); + Assert.assertEquals("Coldcard 6BA6CFD0", keystore.getLabel()); + Assert.assertEquals("m/45'", keystore.getKeyDerivation().getDerivationPath()); + Assert.assertEquals("6ba6cfd0", keystore.getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals(ExtendedPublicKey.fromDescriptor("tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9"), keystore.getExtendedPublicKey()); + Assert.assertTrue(keystore.isValid()); + } + + @Test + public void importKeystore2b() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Keystore keystore = ccMultisig.getKeystore(ScriptType.P2WSH, getInputStream("cc-multisig-keystore-2.json")); + Assert.assertEquals("Coldcard 6BA6CFD0", keystore.getLabel()); + Assert.assertEquals("m/48'/1'/0'/2'", keystore.getKeyDerivation().getDerivationPath()); + Assert.assertEquals("6ba6cfd0", keystore.getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals(ExtendedPublicKey.fromDescriptor("Vpub5nUnvPehg1VYQh13dGznx1P9moac3SNUrn3qhU9r85RhXabYbSSBNsNNwyR7akozAZJw1SZmRRjry1zY8PWMuw8Ga1vQZ5qzPjKyTDQwtzs"), keystore.getExtendedPublicKey()); + Assert.assertTrue(keystore.isValid()); + } + + @Test + public void importWallet1() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-1.txt")); + Assert.assertEquals("CC-2-of-4", wallet.getName()); + Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType()); + Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("multi(2,coldcard0f056943,coldcard6ba6cfd0,coldcard747b698e,coldcard7bb026be)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertTrue(wallet.isValid()); + } + + @Test + public void importWallet2() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-2.txt")); + Assert.assertEquals("CC-2-of-4", wallet.getName()); + Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType()); + Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("multi(2,coldcard0f056943,coldcard6ba6cfd0,coldcard747b698e,coldcard7bb026be)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertTrue(wallet.isValid()); + } + + @Test + public void importWalletMultiDeriv() throws ImportException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-multideriv.txt")); + Assert.assertEquals("el-CC-3-of-3-sb-2", wallet.getName()); + Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType()); + Assert.assertEquals(3, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("multi(3,coldcard06b57041,coldcard4b569672,coldcardca9a2b19)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("06b57041", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/0'/0'/2'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6EfEGa5isJbQFSswM5Uptw5BSq2Td1ZDJr3QUNUcMySpC7itZ3ccypVHtLPnvMzKQ2qxrAgH49vhVxRcaQLFbixAVRR8RACrYTp88Uv9h8Z", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertEquals("ca9a2b19", wallet.getKeystores().get(2).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/0'/0'/1'", wallet.getKeystores().get(2).getKeyDerivation().getDerivationPath()); + Assert.assertTrue(wallet.isValid()); + } + + @Test + public void exportWallet1() throws ImportException, ExportException, IOException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + byte[] walletBytes = ByteStreams.toByteArray(getInputStream("cc-multisig-export-1.txt")); + Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ccMultisig.exportWallet(wallet, baos); + byte[] exportedBytes = baos.toByteArray(); + String original = new String(walletBytes); + String exported = new String(exportedBytes); + Assert.assertEquals(original.replaceAll("created on [0-9A-F]+", ""), exported.replace("created by Sparrow", "")); + } + + @Test + public void exportWalletMultiDeriv() throws ImportException, ExportException, IOException { + ColdcardMultisig ccMultisig = new ColdcardMultisig(); + byte[] walletBytes = ByteStreams.toByteArray(getInputStream("cc-multisig-export-multideriv.txt")); + Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ccMultisig.exportWallet(wallet, baos); + byte[] exportedBytes = baos.toByteArray(); + String original = new String(walletBytes); + String exported = new String(exportedBytes); + Assert.assertEquals(original.replaceAll("Exported from Electrum", ""), exported.replace("Coldcard Multisig setup file (created by Sparrow)\n#", "")); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/external/ColdcardSinglesigTest.java b/src/test/java/com/sparrowwallet/sparrow/external/ColdcardSinglesigTest.java new file mode 100644 index 00000000..b1a6f0fa --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/external/ColdcardSinglesigTest.java @@ -0,0 +1,25 @@ +package com.sparrowwallet.sparrow.external; + +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.Assert; +import org.junit.Test; + +public class ColdcardSinglesigTest extends ImportExportTest { + @Test + public void testImport() throws ImportException { + ColdcardSinglesig ccSingleSig = new ColdcardSinglesig(); + Wallet wallet = ccSingleSig.importWallet(getInputStream("cc-wallet-dump.txt"), ScriptType.P2PKH); + Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); + + Assert.assertEquals("Coldcard 3D88D0CF", wallet.getName()); + Assert.assertEquals(ScriptType.P2PKH, wallet.getScriptType()); + Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("pkh(keystore1)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertTrue(wallet.isValid()); + Assert.assertEquals("3d88d0cf", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/44'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6AuabxJxEnAJbc8iBE2B5n7hxYAZC5xLjpG7oY1kyhMfz5mN13wLRaGPnCyvLo4Ec5aRSa6ZeMPHMUEABpdKxtcPymJpDG5KPEsLGTApGye", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/external/ElectrumTest.java b/src/test/java/com/sparrowwallet/sparrow/external/ElectrumTest.java new file mode 100644 index 00000000..04e75d3e --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/external/ElectrumTest.java @@ -0,0 +1,86 @@ +package com.sparrowwallet.sparrow.external; + +import com.google.common.io.ByteStreams; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public class ElectrumTest extends ImportExportTest { + @Test + public void testSinglesigImport() throws ImportException { + Electrum electrum = new Electrum(); + Wallet wallet = electrum.importWallet(getInputStream("electrum-singlesig-wallet.json")); + + Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType()); + Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("pkh(trezortest)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("ab543c67", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/84'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertTrue(wallet.isValid()); + } + + @Test + public void testSinglesigExport() throws ImportException, ExportException, IOException { + Electrum electrum = new Electrum(); + byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-singlesig-wallet.json")); + Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + electrum.exportWallet(wallet, baos); + + wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray())); + Assert.assertTrue(wallet.isValid()); + Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType()); + Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("pkh(trezortest)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("ab543c67", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/84'/0'/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + } + + @Test + public void testMultisigImport() throws ImportException { + Electrum electrum = new Electrum(); + Wallet wallet = electrum.importWallet(getInputStream("electrum-multisig-wallet.json")); + + Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType()); + Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("multi(2,coldcard6ba6cfd0,coldcard747b698e,coldcard7bb026be,coldcard0f056943)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("6ba6cfd0", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertEquals("7bb026be", wallet.getKeystores().get(2).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(2).getKeyDerivation().getDerivationPath()); + Assert.assertTrue(wallet.isValid()); + } + + @Test + public void testMultisigExport() throws ImportException, ExportException, IOException { + Electrum electrum = new Electrum(); + byte[] walletBytes = ByteStreams.toByteArray(getInputStream("electrum-multisig-wallet.json")); + Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + electrum.exportWallet(wallet, baos); + + wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray())); + Assert.assertTrue(wallet.isValid()); + Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType()); + Assert.assertEquals(2, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("multi(2,coldcard6ba6cfd0,coldcard747b698e,coldcard7bb026be,coldcard0f056943)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("6ba6cfd0", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub6FFEQVG6QR28chQzgSJ7Gjx5j5BGLkCMgZ9bc41YJCXfwYiCKUQdcwm4Fe1stvzRjosz5udMedYZFRL56AeZXCsiVmnVUysio4jkAKTukmN", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertEquals("7bb026be", wallet.getKeystores().get(2).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/48'/1'/0'/1'", wallet.getKeystores().get(2).getKeyDerivation().getDerivationPath()); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/external/ImportExportTest.java b/src/test/java/com/sparrowwallet/sparrow/external/ImportExportTest.java new file mode 100644 index 00000000..4f38121a --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/external/ImportExportTest.java @@ -0,0 +1,10 @@ +package com.sparrowwallet.sparrow.external; + +import java.io.InputStream; + +public class ImportExportTest { + + protected InputStream getInputStream(String filename) { + return this.getClass().getResourceAsStream("/com/sparrowwallet/sparrow/external/" + filename); + } +} diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-1.txt b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-1.txt new file mode 100644 index 00000000..6868af5f --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-1.txt @@ -0,0 +1,11 @@ +# Coldcard Multisig setup file (created on 0F056943) +# +Name: CC-2-of-4 +Policy: 2 of 4 +Derivation: m/48'/1'/0'/2' +Format: P2WSH + +0F056943: xpub6EfEGa5isJbQFSswM5Uptw5BSq2Td1ZDJr3QUNUcMySpC7itZ3ccypVHtLPnvMzKQ2qxrAgH49vhVxRcaQLFbixAVRR8RACrYTp88Uv9h8Z +6BA6CFD0: xpub6FFEQVG6QR28giDuML74Y7EMPwqEiKftNjScLzg5WKM41bf6LMP2XspjBgNp28tvkNUZdokmTY4TcRbuGZBSMvNoUECrKW1y3TBPeQJVmAg +747B698E: xpub6Eb6Z1xtmWRiWKgRpHf6dHiEagGd6FLiBXrnma1nFK4PGRYqSVqVyJaxna5Mb8etSP4ATKVAvKnXG1a9HZauoAawuSDJT5RgH2HqEVHZVHY +7BB026BE: xpub6FMGmJccz6dqLo9TMmXYPpZ7HUDm71RHHSTXqTUgkyP9TZmF2uexoB7qttEBtHaotopPwfAVfKfwmdEjCGabVpND1m7ix2AW2LxPuNfLZhi diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-2.txt b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-2.txt new file mode 100644 index 00000000..1291ff96 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-2.txt @@ -0,0 +1,11 @@ +# Coldcard Multisig setup file (created on 0F056943) +# +Name: CC-2-of-4 +Policy: 2 of 4 +Derivation: m/48'/1'/0'/1' +Format: P2WSH-P2SH + +0F056943: tpubDF2rnouQaaYrUEy2JM1YD3RFzew4onawGM4X2Re67gguTf5CbHonBRiFGe3Xjz7DK88dxBFGf2i7K1hef3PM4cFKyUjcbJXddaY9F5tJBoP +6BA6CFD0: tpubDFcrvj5n7gyatVbr8dHCUfHT4CGvL8hREBjtxc4ge7HZgqNuPhFimPRtVg6fRRwfXiQthV9EBjNbwbpgV2VoQeL1ZNXoAWXxP2L9vMtRjax +747B698E: tpubDExj5FnaUnPAjjgzELoSiNRkuXJG8Cm1pbdiA4Hc5vkAZHphibeVcUp6mqH5LuNVKbtLVZxVSzyja5X26Cfmx6pzRH6gXBUJAH7MiqwNyuM +7BB026BE: tpubDFiuHYSJhNbHaGtB5skiuDLg12tRboh2uVZ6KGXxr8WVr28pLcS7F3gv8SsHFa2tm1jtx3VAuw56YfgRkdo6DXyfp51oygTKY3nJFT5jBMt diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-multideriv.txt b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-multideriv.txt new file mode 100644 index 00000000..a667731f --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-export-multideriv.txt @@ -0,0 +1,14 @@ +# Exported from Electrum +Name: el-CC-3-of-3-sb-2 +Policy: 3 of 3 +Format: P2WSH + +# derivation: m/48'/0'/0'/2' +06B57041: xpub6EfEGa5isJbQFSswM5Uptw5BSq2Td1ZDJr3QUNUcMySpC7itZ3ccypVHtLPnvMzKQ2qxrAgH49vhVxRcaQLFbixAVRR8RACrYTp88Uv9h8Z + +# derivation: m/48'/0'/0'/2' +4B569672: xpub6FFEQVG6QR28giDuML74Y7EMPwqEiKftNjScLzg5WKM41bf6LMP2XspjBgNp28tvkNUZdokmTY4TcRbuGZBSMvNoUECrKW1y3TBPeQJVmAg + +# derivation: m/48'/0'/0'/1' +CA9A2B19: xpub6Eb6Z1xtmWRiWKgRpHf6dHiEagGd6FLiBXrnma1nFK4PGRYqSVqVyJaxna5Mb8etSP4ATKVAvKnXG1a9HZauoAawuSDJT5RgH2HqEVHZVHY + diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-1.json b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-1.json new file mode 100644 index 00000000..2a709fe3 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-1.json @@ -0,0 +1,9 @@ +{ + "p2sh_deriv": "m/45'", + "p2sh": "tpubD8NXmKsmWp3a3DXhbihAYbYLGaRNVdTnr6JoSxxfXYQcmwVtW2hv8QoDwng6JtEonmJoL3cNEwfd2cLXMpGezwZ2vL2dQ7259bueNKj9C8n", + "p2wsh_p2sh_deriv": "m/48'/1'/0'/1'", + "p2wsh_p2sh": "Upub5T4XUooQzDXL58NCHk8ZCw9BsRSLCtnyHeZEExAq1XdnBFXiXVrHFuvvmh3TnCR7XmKHxkwqdACv68z7QKT1vwru9L1SZSsw8B2fuBvtSa6", + "p2wsh_deriv": "m/48'/1'/0'/2'", + "p2wsh": "Vpub5mtnnUUL8u4oyRf5d2NZJqDypgmpx8FontedpqxNyjXTi6fLp8fmpp2wedS6UyuNpDgLDoVH23c6rYpFSEfB9jhdbD8gek2stjxhwJeE1Eq", + "xfp": "0F056943" +} diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-2.json b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-2.json new file mode 100644 index 00000000..3d6ed6b3 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-multisig-keystore-2.json @@ -0,0 +1,9 @@ +{ + "p2sh_deriv": "m/45'", + "p2sh": "tpubD9429UXFGCTKJ9NdiNK4rC5ygqSUkginycYHccqSg5gkmyQ7PZRHNjk99M6a6Y3NY8ctEUUJvCu6iCCui8Ju3xrHRu3Ez1CKB4ZFoRZDdP9", + "p2wsh_p2sh_deriv": "m/48'/1'/0'/1'", + "p2wsh_p2sh": "Upub5TeXciynXKx4VP1282QDUZ1NvxnBjEuTFVEcB8bRXxESQRqRKuJDqseZzj6bTeFZkMbYi4qo9rsQij79EJZUGywajDod8etFscpgaTSShLd", + "p2wsh_deriv": "m/48'/1'/0'/2'", + "p2wsh": "Vpub5nUnvPehg1VYQh13dGznx1P9moac3SNUrn3qhU9r85RhXabYbSSBNsNNwyR7akozAZJw1SZmRRjry1zY8PWMuw8Ga1vQZ5qzPjKyTDQwtzs", + "xfp": "6BA6CFD0" +} diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/cc-wallet-dump.txt b/src/test/resources/com/sparrowwallet/sparrow/external/cc-wallet-dump.txt new file mode 100644 index 00000000..a9301607 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/cc-wallet-dump.txt @@ -0,0 +1,97 @@ +# Coldcard Wallet Summary File + +## Wallet operates on blockchain: Bitcoin + +For BIP44, this is coin_type '0', and internally we use symbol BTC for this blockchain. + +## Top-level, 'master' extended public key ('m/'): + +xpub661MyMwAqRbcGQU2MzQdLtxKvfa9shyo1vUGkxETFtDNGjggQMNMd5rTZfbKR25yCXHgtpwwko4Cyq1PkzLoEGRSmNy5GnnhCkWERN1wJSy + +Derived public keys, as may be needed for different systems: + + +## For Bitcoin Core: m/{account}'/{change}'/{idx}' + +m => xpub661MyMwAqRbcGQU2MzQdLtxKvfa9shyo1vUGkxETFtDNGjggQMNMd5rTZfbKR25yCXHgtpwwko4Cyq1PkzLoEGRSmNy5GnnhCkWERN1wJSy + +... first 5 receive addresses (account=0, change=0): + +m/0'/0'/0' => 1AaTq7W3Mw8J4UGpKL1Sc4DwWpNQSBgeHa +m/0'/0'/1' => 1GRDRoXkjPue2SPXvL8XZz5paK2Te4tbxZ +m/0'/0'/2' => 1Gxwx9pxvsmQCTf3Yx2Yo2jfSqjeHTgqJA +m/0'/0'/3' => 13ECwnbfj99my2edurXyzVtGW8NYGHq7u1 +m/0'/0'/4' => 1D8KQ8Yctm4WesGsviQ8ZWApSbh7PAnLqy + + +## For Bitcoin Core (Segregated Witness, P2PKH): m/{account}'/{change}'/{idx}' + +m => xpub661MyMwAqRbcGQU2MzQdLtxKvfa9shyo1vUGkxETFtDNGjggQMNMd5rTZfbKR25yCXHgtpwwko4Cyq1PkzLoEGRSmNy5GnnhCkWERN1wJSy +# SLIP-132 style +m => zpub6jftahH18ngZxzrG2hysm59LGbs3kwxnr9WiKk2E1ty8NwK8ufhUsDAjc5WVQqPp1oXJPn94g7mJkQEXCPAppjneW4MvScRfkCdXCXk1zgB + +... first 5 receive addresses (account=0, change=0): + +m/0'/0'/0' => bc1qdyx5z3p6nlxrjfay7mhefx8t4jscqu6sueg0vu +m/0'/0'/1' => bc1q4y0ruupprurvl9umalmt0u9ztju0qxfqfrqwhw +m/0'/0'/2' => bc1q4u029f45f3xegw2z72kmd4xcfl8dgsvg58u7xn +m/0'/0'/3' => bc1qrph6zs0yzrxg5j52qzp4s9njmp3lqj88tdv7ur +m/0'/0'/4' => bc1qs5pu0x8aqjslxvng7hq4w743gysgrnspxnagtz + + +## For Electrum (not BIP44): m/{change}/{idx} + +m => xpub661MyMwAqRbcGQU2MzQdLtxKvfa9shyo1vUGkxETFtDNGjggQMNMd5rTZfbKR25yCXHgtpwwko4Cyq1PkzLoEGRSmNy5GnnhCkWERN1wJSy + +... first 5 receive addresses (account=0, change=0): + +m/0/0 => 16PYSMXY2BatS8FzbzwrAqM1HrHhxPzz2A +m/0/1 => 1JccZ1v4rZ3WhU9JDSVv1z1GwgwYQpJr7m +m/0/2 => 1MJ5TicEUw169T8qp6E2QUuLkeECz2QD27 +m/0/3 => 1J3f5S8v6VVHqHCfs7ECeVhvAbpV6EUKna +m/0/4 => 1C8A19VJL9NPfKNp6TiebQTJqtVNwbJ1hp + + +## For BIP44 / Electrum: m/44'/0'/{account}'/{change}/{idx} + +m/44'/0' => xpub6AuabxJxEnAJbc8iBE2B5n7hxYAZC5xLjpG7oY1kyhMfz5mN13wLRaGPnCyvLo4Ec5aRSa6ZeMPHMUEABpdKxtcPymJpDG5KPEsLGTApGye + +... first 5 receive addresses (account=0, change=0): + +m/44'/0'/0'/0/0 => 1NDKGzwrhz8n7euEapPRZkktiyXBEXFyKf +m/44'/0'/0'/0/1 => 1NK9ir2VTiYfVGvSKUwftqy1HQWJPwtSrC +m/44'/0'/0'/0/2 => 1L8cB6b3WEzkCqTFGSWWyEKZMqiytP8TTX +m/44'/0'/0'/0/3 => 15grLkNbrKakMFE2eJWXa6hQNJRzswvsK4 +m/44'/0'/0'/0/4 => 16714S67jGeL9zp6qQjLJd9WpsswoTVgY7 + + +## For BIP49 (P2WPKH-nested-in-P2SH): m/49'/0'/{account}'/{change}/{idx} + +m/49'/0' => xpub6ApwLnWVoU6m4aGMh1kVbwA8CACF2m31sGkJbSx15KWjifbBnE1UHjvToBJZpqDmcMD859Si6DrRPace7Q4TBMiGQwvHttjJQiwB7TL6j8H +# SLIP-132 style +m/49'/0' => ypub6VfCeTBQx9eEusTUXNY7p2FdN8LgyP2WnPGXNqqtTKtcmmQR2tB2uoabpPG9pjsh1zKvpd3GYtCyGsECq6UTybPsHHciUoYngSzpW25khLg + +... first 5 receive addresses (account=0, change=0): + +m/49'/0'/0'/0/0 => 3KfeHRpD4VbPnm928NVx5QBsZ4Si9L3TJH +m/49'/0'/0'/0/1 => 3Fsj1s12r12ykx7cQ6VPzXLYe2kHEHP1zk +m/49'/0'/0'/0/2 => 35Xezi189cXAx3DZ9PLUwzhVqejB22GSKc +m/49'/0'/0'/0/3 => 3BD6i8i6jYg83CCNsEo4b8hruECmFeuPNd +m/49'/0'/0'/0/4 => 3J3pVvhYt4LmGGRsTfkrnWukLg2yXd45oQ + + +## For BIP84 (Native Segwit P2PKH): m/84'/0'/{account}'/{change}/{idx} + +m/84'/0' => xpub6BUBVXTHPtiWZuJT7ZVArTEXi5FcGNX4d4TMLTuRSCcVEQ37BASyq17BoSBxwLgaVBvyR9GbtnVeKhAAwdmqHppzrukRk55XHgc32idASq2 +# SLIP-132 style +m/84'/0' => zpub6q8i6ro7hFoUGVggnH4RGdRY41YW9cW4THVnuFhCCDNFLbfZgUn758RTqr78w9zRJUAav6Tip7Ck6GPJP2brtJCCbb9GutiVq8jKoqNszsS + +... first 5 receive addresses (account=0, change=0): + +m/84'/0'/0'/0/0 => bc1qkwyhuqeu37f7erej85fwwtn33cmupnmra4rf2k +m/84'/0'/0'/0/1 => bc1qmng3kwg97p0emk8p8w4faym8y9w8zqeld90k2a +m/84'/0'/0'/0/2 => bc1qgaqzjdnztrle7v4qg3yvnwnu5rndpkdn3gftxm +m/84'/0'/0'/0/3 => bc1qc703cjt0jvx2adsjfhg2dcfp8k34j76xymkqdl +m/84'/0'/0'/0/4 => bc1qk3ru377gs5wj0e8psyse2jrwxn5jym3kx8ufla + + diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/electrum-multisig-wallet.json b/src/test/resources/com/sparrowwallet/sparrow/external/electrum-multisig-wallet.json new file mode 100644 index 00000000..cd304af3 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/electrum-multisig-wallet.json @@ -0,0 +1,2 @@ +{"x2/": {"xpub": "Upub5TeXciynXKx4VP1282QDUZ1NvxnBjEuTFVEcB8bRXxESQRqRKuJDqseZzj6bTeFZkMbYi4qo9rsQij79EJZUGywajDod8etFscpgaTSShLd", "hw_type": "coldcard", "ckcc_xfp": 3503269483, "label": "Coldcard 6BA6CFD0", "derivation": "m/48'/1'/0'/1'", "type": "hardware"}, "x3/": {"xpub": "Upub5SzPmFgatRMeLd6ADjvTiG9gnHoXXJy3qu8RNapLymh3GtHDeogzgy2nGtH1P7gPYF4zW9f4R8UYMCoUqUjSpSSZb8NWVKpbesbtP21e6Z4", "hw_type": "coldcard", "ckcc_xfp": 2389277556, "label": "Coldcard 747B698E", "derivation": "m/48'/1'/0'/1'", "type": "hardware"}, "x4/": {"xpub": "Upub5TkZyYLK71ZmBAHM5Gsju74bsoPgzuu4vo3oXo4hjyTNZcbLGpUcKXubdVsDHnLnyevYxdBjt4ZuKnxtVurm5sbEyvHdwpod2eGpubnG9yQ", "hw_type": "coldcard", "ckcc_xfp": 3190206587, "label": "Coldcard 7BB026BE", "derivation": "m/48'/1'/0'/1'", "type": "hardware"}, "x1/": {"xpub": "Upub5T4XUooQzDXL58NCHk8ZCw9BsRSLCtnyHeZEExAq1XdnBFXiXVrHFuvvmh3TnCR7XmKHxkwqdACv68z7QKT1vwru9L1SZSsw8B2fuBvtSa6", "hw_type": "coldcard", "ckcc_xfp": 1130956047, "label": "Coldcard 0F056943", "derivation": "m/48'/1'/0'/1'", "type": "hardware"}, "wallet_type": "2of4", "use_encryption": false, "seed_version": 17} + diff --git a/src/test/resources/com/sparrowwallet/sparrow/external/electrum-singlesig-wallet.json b/src/test/resources/com/sparrowwallet/sparrow/external/electrum-singlesig-wallet.json new file mode 100644 index 00000000..c1ea7ba6 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/external/electrum-singlesig-wallet.json @@ -0,0 +1,25 @@ +{ + "fiat_value": {}, + "invoices": {}, + "keystore": { + "derivation": "m/84'/0'/0'", + "hw_type": "trezor", + "label": "trezortest", + "root_fingerprint": "ab543c67", + "soft_device_id": "65465645", + "type": "hardware", + "xpub": "ypub6a5Vi9w1Z6ZcTzc7Wo5jUq3au3KiHNBrbffpPSuRgCuYzeXRa8aCF1RCGqyTtqeM9SznqPDv7Hu78hwdos4aKSZKN7Uv4thD4noPYpzrfN9" + }, + "labels": {}, + "payment_requests": {}, + "prevouts_by_scripthash": {}, + "seed_version": 28, + "spent_outpoints": {}, + "transactions": {}, + "tx_fees": {}, + "txi": {}, + "txo": {}, + "use_encryption": false, + "verified_tx3": {}, + "wallet_type": "standard" +} \ No newline at end of file