coldcard singlesig new feature, private key handling

This commit is contained in:
Craig Raw 2020-05-06 17:14:12 +02:00
parent 6b651ec63a
commit 2de90dfdc1
16 changed files with 186 additions and 249 deletions

2
drongo

@ -1 +1 @@
Subproject commit 27dda9157649b35bc4450361c2f31d489eef492b
Subproject commit c5042cf130457233955aa4c72b1ad543bdfcb171

View file

@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.crypto.ChildNumber;
import com.sparrowwallet.drongo.wallet.Keystore;
@ -363,7 +363,7 @@ public class DevicePane extends TitledPane {
keystore.setSource(KeystoreSource.HW_USB);
keystore.setWalletModel(device.getModel());
keystore.setKeyDerivation(new KeyDerivation(device.getFingerprint(), derivationPath));
keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(xpub));
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(xpub));
EventManager.get().post(new KeystoreImportEvent(keystore));
});

View file

@ -0,0 +1,33 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Bip39Calculator;
import com.sparrowwallet.drongo.wallet.Keystore;
import com.sparrowwallet.drongo.wallet.WalletModel;
import java.util.List;
public class Bip39 implements KeystoreMnemonicImport {
@Override
public Keystore getKeystore(ScriptType scriptType, List<String> mnemonicWords, String passphrase) throws ImportException {
Bip39Calculator bip39Calculator = new Bip39Calculator();
return null;
}
@Override
public WalletModel getWalletModel() {
return WalletModel.SPARROW;
}
@Override
public String getKeystoreImportDescription() {
return "Import your 12 to 24 word mnemonic and optional passphrase";
}
@Override
public String getName() {
return null;
}
}

View file

@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.io;
import com.google.common.io.CharStreams;
import com.google.gson.Gson;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.policy.Policy;
@ -26,11 +26,6 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor
return "Coldcard Multisig";
}
@Override
public PolicyType getKeystorePolicyType() {
return PolicyType.MULTI;
}
@Override
public WalletModel getWalletModel() {
return WalletModel.COLDCARD;
@ -47,13 +42,13 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor
if(scriptType.equals(ScriptType.P2SH)) {
keystore.setKeyDerivation(new KeyDerivation(cck.xfp, cck.p2sh_deriv));
keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(cck.p2sh));
keystore.setExtendedPublicKey(ExtendedKey.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));
keystore.setExtendedPublicKey(ExtendedKey.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));
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(cck.p2wsh));
} else {
throw new ImportException("Correct derivation not found for script type: " + scriptType);
}
@ -61,7 +56,7 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor
return keystore;
}
public static class ColdcardKeystore {
private static class ColdcardKeystore {
public String p2sh_deriv;
public String p2sh;
public String p2wsh_p2sh_deriv;
@ -118,7 +113,7 @@ public class ColdcardMultisig implements MultisigWalletImport, KeystoreFileImpor
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
keystore.setWalletModel(WalletModel.COLDCARD);
keystore.setKeyDerivation(new KeyDerivation(key, derivation));
keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(value));
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(value));
wallet.getKeystores().add(keystore);
}
}

View file

@ -1,40 +1,30 @@
package com.sparrowwallet.sparrow.io;
import com.google.common.io.CharStreams;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import com.sparrowwallet.drongo.ExtendedKey;
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.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
import static com.sparrowwallet.drongo.protocol.ScriptType.*;
public class ColdcardSinglesig implements KeystoreFileImport, SinglesigWalletImport {
public static final List<ScriptType> ALLOWED_SCRIPT_TYPES = List.of(P2PKH, P2SH_P2WPKH, P2WPKH);
import java.lang.reflect.Type;
import java.util.Map;
public class ColdcardSinglesig implements KeystoreFileImport {
@Override
public String getName() {
return "Coldcard";
}
@Override
public PolicyType getKeystorePolicyType() {
return PolicyType.SINGLE;
}
@Override
public String getKeystoreImportDescription() {
return "Import file created by using the Advanced > Dump Summary feature on your Coldcard";
return "Import file created by using the Advanced > MicroSD > Export Wallet > Generic JSON feature on your Coldcard";
}
@Override
@ -42,72 +32,55 @@ public class ColdcardSinglesig implements KeystoreFileImport, SinglesigWalletImp
return WalletModel.COLDCARD;
}
@Override
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
Wallet wallet = importWallet(scriptType, inputStream, password);
return wallet.getKeystores().get(0);
}
@Override
public Wallet importWallet(ScriptType scriptType, InputStream inputStream, String password) 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<String> 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.getDefaultDerivationPath().startsWith(key)) {
ExtendedPublicKey extPubKey = ExtendedPublicKey.fromDescriptor(value);
Keystore keystore = new Keystore();
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
keystore.setWalletModel(WalletModel.COLDCARD);
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";
}
@Override
public boolean isEncrypted(File file) {
return false;
}
@Override
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
try {
Gson gson = new Gson();
Type stringStringMap = new TypeToken<Map<String, JsonElement>>() {
}.getType();
Map<String, JsonElement> map = gson.fromJson(new InputStreamReader(inputStream), stringStringMap);
if (map.get("xfp") == null) {
throw new ImportException("This is not a valid Coldcard wallet export");
}
String masterFingerprint = map.get("xfp").getAsString();
for (String key : map.keySet()) {
if (key.startsWith("bip")) {
ColdcardKeystore ck = gson.fromJson(map.get(key), ColdcardKeystore.class);
if(ck.name != null) {
ScriptType ckScriptType = ScriptType.valueOf(ck.name.replace("p2wpkh-p2sh", "p2sh_p2wpkh").toUpperCase());
if(ckScriptType.equals(scriptType)) {
Keystore keystore = new Keystore();
keystore.setLabel("Coldcard " + masterFingerprint.toUpperCase());
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
keystore.setWalletModel(WalletModel.COLDCARD);
keystore.setKeyDerivation(new KeyDerivation(masterFingerprint, ck.deriv));
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(ck.xpub));
return keystore;
}
}
}
}
} catch (Exception e) {
throw new ImportException(e);
}
throw new ImportException("Correct derivation not found for script type: " + scriptType);
}
private static class ColdcardKeystore {
public String deriv;
public String name;
public String xpub;
public String xfp;
}
}

View file

@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.io;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.crypto.ECKey;
@ -27,11 +27,6 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult
return "Electrum";
}
@Override
public PolicyType getKeystorePolicyType() {
return PolicyType.SINGLE;
}
@Override
public WalletModel getWalletModel() {
return WalletModel.ELECTRUM;
@ -111,12 +106,12 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult
}
keystore.setWalletModel(WalletModel.ELECTRUM);
}
ExtendedPublicKey xPub = ExtendedPublicKey.fromDescriptor(ek.xpub);
ExtendedKey xPub = ExtendedKey.fromDescriptor(ek.xpub);
keystore.setKeyDerivation(new KeyDerivation(ek.root_fingerprint, ek.derivation));
keystore.setExtendedPublicKey(xPub);
wallet.getKeystores().add(keystore);
ExtendedPublicKey.XpubHeader xpubHeader = ExtendedPublicKey.XpubHeader.fromXpub(ek.xpub);
ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromExtendedKey(ek.xpub);
scriptType = xpubHeader.getDefaultScriptType();
}
@ -170,7 +165,7 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult
throw new ExportException("Could not export a wallet with a " + wallet.getPolicyType() + " policy");
}
ExtendedPublicKey.XpubHeader xpubHeader = ExtendedPublicKey.XpubHeader.fromScriptType(wallet.getScriptType());
ExtendedKey.Header xpubHeader = ExtendedKey.Header.fromScriptType(wallet.getScriptType());
int index = 1;
for(Keystore keystore : wallet.getKeystores()) {

View file

@ -4,7 +4,6 @@ import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.wallet.WalletModel;
public interface KeystoreImport extends Import {
PolicyType getKeystorePolicyType();
WalletModel getWalletModel();
String getKeystoreImportDescription();
}

View file

@ -3,6 +3,8 @@ package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
import java.util.List;
public interface KeystoreMnemonicImport extends KeystoreImport {
Keystore getKeystore(ScriptType scriptType, String[] mnemonicWords, String passphrase) throws ImportException;
Keystore getKeystore(ScriptType scriptType, List<String> mnemonicWords, String passphrase) throws ImportException;
}

View file

@ -1,7 +1,7 @@
package com.sparrowwallet.sparrow.io;
import com.google.gson.*;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.wallet.Wallet;
@ -20,8 +20,8 @@ public class Storage {
private Storage() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(ExtendedPublicKey.class, new ExtendedPublicKeySerializer());
gsonBuilder.registerTypeAdapter(ExtendedPublicKey.class, new ExtendedPublicKeyDeserializer());
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeySerializer());
gsonBuilder.registerTypeAdapter(ExtendedKey.class, new ExtendedPublicKeyDeserializer());
gson = gsonBuilder.setPrettyPrinting().create();
}
@ -95,17 +95,17 @@ public class Storage {
return new File(System.getProperty("user.home"));
}
private static class ExtendedPublicKeySerializer implements JsonSerializer<ExtendedPublicKey> {
private static class ExtendedPublicKeySerializer implements JsonSerializer<ExtendedKey> {
@Override
public JsonElement serialize(ExtendedPublicKey src, Type typeOfSrc, JsonSerializationContext context) {
public JsonElement serialize(ExtendedKey src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.toString());
}
}
private static class ExtendedPublicKeyDeserializer implements JsonDeserializer<ExtendedPublicKey> {
private static class ExtendedPublicKeyDeserializer implements JsonDeserializer<ExtendedKey> {
@Override
public ExtendedPublicKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ExtendedPublicKey.fromDescriptor(json.getAsJsonPrimitive().getAsString());
public ExtendedKey deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return ExtendedKey.fromDescriptor(json.getAsJsonPrimitive().getAsString());
}
}
}

View file

@ -1,6 +1,6 @@
package com.sparrowwallet.sparrow.wallet;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.KeyDerivation;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.wallet.Keystore;
@ -83,8 +83,8 @@ public class KeystoreController extends WalletFormController implements Initiali
}
});
xpub.textProperty().addListener((observable, oldValue, newValue) -> {
if(ExtendedPublicKey.isValid(newValue)) {
keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(newValue));
if(ExtendedKey.isValid(newValue)) {
keystore.setExtendedPublicKey(ExtendedKey.fromDescriptor(newValue));
EventManager.get().post(new SettingsChangedEvent(walletForm.getWallet()));
}
});
@ -107,7 +107,7 @@ public class KeystoreController extends WalletFormController implements Initiali
validationSupport.registerValidator(xpub, Validator.combine(
Validator.createEmptyValidator("xPub is required"),
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "xPub is invalid", !ExtendedPublicKey.isValid(newValue))
(Control c, String newValue) -> ValidationResult.fromErrorIf( c, "xPub is invalid", !ExtendedKey.isValid(newValue))
));
validationSupport.registerValidator(derivation, Validator.combine(

View file

@ -1,7 +1,7 @@
package com.sparrowwallet.sparrow.io;
import com.google.common.io.ByteStreams;
import com.sparrowwallet.drongo.ExtendedPublicKey;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore;
@ -15,46 +15,46 @@ public class ColdcardMultisigTest extends IoTest {
@Test
public void importKeystore1() throws ImportException {
ColdcardMultisig ccMultisig = new ColdcardMultisig();
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH_P2WSH, getInputStream("cc-multisig-keystore-1.json"));
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH_P2WSH, getInputStream("cc-multisig-keystore-1.json"), null);
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.assertEquals(ExtendedKey.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"));
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH_P2WPKH, getInputStream("cc-multisig-keystore-1.json"), null);
}
@Test
public void importKeystore2() throws ImportException {
ColdcardMultisig ccMultisig = new ColdcardMultisig();
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH, getInputStream("cc-multisig-keystore-2.json"));
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2SH, getInputStream("cc-multisig-keystore-2.json"), null);
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.assertEquals(ExtendedKey.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"));
Keystore keystore = ccMultisig.getKeystore(ScriptType.P2WSH, getInputStream("cc-multisig-keystore-2.json"), null);
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.assertEquals(ExtendedKey.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"));
Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-1.txt"), null);
Assert.assertEquals("CC-2-of-4", wallet.getName());
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
@ -66,7 +66,7 @@ public class ColdcardMultisigTest extends IoTest {
@Test
public void importWallet2() throws ImportException {
ColdcardMultisig ccMultisig = new ColdcardMultisig();
Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-2.txt"));
Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-2.txt"), null);
Assert.assertEquals("CC-2-of-4", wallet.getName());
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());
@ -78,7 +78,7 @@ public class ColdcardMultisigTest extends IoTest {
@Test
public void importWalletMultiDeriv() throws ImportException {
ColdcardMultisig ccMultisig = new ColdcardMultisig();
Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-multideriv.txt"));
Wallet wallet = ccMultisig.importWallet(getInputStream("cc-multisig-export-multideriv.txt"), null);
Assert.assertEquals("el-CC-3-of-3-sb-2", wallet.getName());
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WSH, wallet.getScriptType());
@ -96,7 +96,7 @@ public class ColdcardMultisigTest extends IoTest {
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));
Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes), null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ccMultisig.exportWallet(wallet, baos);
byte[] exportedBytes = baos.toByteArray();
@ -109,7 +109,7 @@ public class ColdcardMultisigTest extends IoTest {
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));
Wallet wallet = ccMultisig.importWallet(new ByteArrayInputStream(walletBytes), null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ccMultisig.exportWallet(wallet, baos);
byte[] exportedBytes = baos.toByteArray();

View file

@ -1,8 +1,8 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.ExtendedKey;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.Keystore;
import org.junit.Assert;
import org.junit.Test;
@ -10,16 +10,24 @@ public class ColdcardSinglesigTest extends IoTest {
@Test
public void testImport() throws ImportException {
ColdcardSinglesig ccSingleSig = new ColdcardSinglesig();
Wallet wallet = ccSingleSig.importWallet(ScriptType.P2PKH, getInputStream("cc-wallet-dump.txt"));
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Keystore keystore = ccSingleSig.getKeystore(ScriptType.P2SH_P2WPKH, getInputStream("cc-singlesig-keystore-1.json"), null);
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());
Assert.assertEquals("Coldcard 0F056943", keystore.getLabel());
Assert.assertEquals("m/49'/1'/123'", keystore.getKeyDerivation().getDerivationPath());
Assert.assertEquals("0f056943", keystore.getKeyDerivation().getMasterFingerprint());
Assert.assertEquals(ExtendedKey.fromDescriptor("tpubDCDqt7XXvhAdy1MpSze5nMJA9x8DrdRaKALRRPasfxyHpiqWWEAr9cbDBQ9BcX7cB3up98Pk97U2QQ3xrvQsi5dNPmRYYhdcsKY9wwEY87T"), keystore.getExtendedPublicKey());
Assert.assertTrue(keystore.isValid());
}
@Test
public void testImportWitness() throws ImportException {
ColdcardSinglesig ccSingleSig = new ColdcardSinglesig();
Keystore keystore = ccSingleSig.getKeystore(ScriptType.P2WPKH, getInputStream("cc-singlesig-keystore-1.json"), null);
Assert.assertEquals("Coldcard 0F056943", keystore.getLabel());
Assert.assertEquals("m/84'/1'/123'", keystore.getKeyDerivation().getDerivationPath());
Assert.assertEquals("0f056943", keystore.getKeyDerivation().getMasterFingerprint());
Assert.assertEquals(ExtendedKey.fromDescriptor("tpubDC7jGaaSE66VDB6VhEDFYQSCAyugXmfnMnrMVyHNzW9wryyTxvha7TmfAHd7GRXrr2TaAn2HXn9T8ep4gyNX1bzGiieqcTUNcu2poyntrET"), keystore.getExtendedPublicKey());
Assert.assertTrue(keystore.isValid());
}
}

View file

@ -17,14 +17,14 @@ public class ECIESOutputStreamTest extends IoTest {
public void encrypt() throws ImportException, ExportException {
Electrum electrum = new Electrum();
ECKey decryptionKey = ECKey.createKeyPbkdf2HmacSha512("pass");
Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey)));
Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey)), null);
ECKey encyptionKey = ECKey.fromPublicOnly(decryptionKey);
ByteArrayOutputStream dummyFileOutputStream = new ByteArrayOutputStream();
electrum.exportWallet(wallet, new DeflaterOutputStream(new ECIESOutputStream(dummyFileOutputStream, encyptionKey)));
ByteArrayInputStream dummyFileInputStream = new ByteArrayInputStream(dummyFileOutputStream.toByteArray());
wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(dummyFileInputStream, decryptionKey)));
wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(dummyFileInputStream, decryptionKey)), null);
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType());

View file

@ -15,7 +15,7 @@ public class ElectrumTest extends IoTest {
@Test
public void testSinglesigImport() throws ImportException {
Electrum electrum = new Electrum();
Wallet wallet = electrum.importWallet(getInputStream("electrum-singlesig-wallet.json"));
Wallet wallet = electrum.importWallet(getInputStream("electrum-singlesig-wallet.json"), null);
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
@ -31,11 +31,11 @@ public class ElectrumTest extends IoTest {
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));
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
electrum.exportWallet(wallet, baos);
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()));
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()), null);
Assert.assertTrue(wallet.isValid());
Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WPKH, wallet.getScriptType());
@ -49,7 +49,7 @@ public class ElectrumTest extends IoTest {
@Test
public void testMultisigImport() throws ImportException {
Electrum electrum = new Electrum();
Wallet wallet = electrum.importWallet(getInputStream("electrum-multisig-wallet.json"));
Wallet wallet = electrum.importWallet(getInputStream("electrum-multisig-wallet.json"), null);
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());
@ -67,11 +67,11 @@ public class ElectrumTest extends IoTest {
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));
Wallet wallet = electrum.importWallet(new ByteArrayInputStream(walletBytes), null);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
electrum.exportWallet(wallet, baos);
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()));
wallet = electrum.importWallet(new ByteArrayInputStream(baos.toByteArray()), null);
Assert.assertTrue(wallet.isValid());
Assert.assertEquals(PolicyType.MULTI, wallet.getPolicyType());
Assert.assertEquals(ScriptType.P2SH_P2WSH, wallet.getScriptType());

View file

@ -0,0 +1,29 @@
{
"chain": "XTN",
"xfp": "0F056943",
"xpub": "tpubD6NzVbkrYhZ4XzL5Dhayo67Gorv1YMS7j8pRUvVMd5odC2LBPLAygka9p7748JtSq82FNGPppFEz5xxZUdasBRCqJqXvUHq6xpnsMcYJzeh",
"account": 123,
"bip44": {
"deriv": "m/44'/1'/123'",
"first": "n44vs1Rv7T8SANrg2PFGQhzVkhr5Q6jMMD",
"name": "p2pkh",
"xfp": "B7908B26",
"xpub": "tpubDCiHGUNYdRRGoSH22j8YnruUKgguCK1CC2NFQUf9PApeZh8ewAJJWGMUrhggDNK73iCTanWXv1RN5FYemUH8UrVUBjqDb8WF2VoKmDh9UTo"
},
"bip49": {
"_pub": "upub5DMRSsh6mNak9KbcVjJ7xAgHJvbE3Nx22CBTier5C35kv8j7g2q58ywxskBe6JCcAE2VH86CE2aL4MifJyKbRw8Gj9ay7SWvUBkp2DJ7y52",
"deriv": "m/49'/1'/123'",
"first": "2N87V39riUUCd4vmXfDjMWAu9gUCiBji5jB",
"name": "p2wpkh-p2sh",
"xfp": "CEE1D809",
"xpub": "tpubDCDqt7XXvhAdy1MpSze5nMJA9x8DrdRaKALRRPasfxyHpiqWWEAr9cbDBQ9BcX7cB3up98Pk97U2QQ3xrvQsi5dNPmRYYhdcsKY9wwEY87T"
},
"bip84": {
"_pub": "vpub5Y5a91QvDT45EnXQaKeuvJupVvX8f9BiywDcadSTtaeJ1VgJPPXMitnYsqd9k7GnEqh44FKJ5McJfu6KrihFXhAmvSWgm7BAVVK8Gupu4fL",
"deriv": "m/84'/1'/123'",
"first": "tb1qc58ys2dphtphg6yuugdf3d0kufmk0tye044g3l",
"name": "p2wpkh",
"xfp": "78CF94E5",
"xpub": "tpubDC7jGaaSE66VDB6VhEDFYQSCAyugXmfnMnrMVyHNzW9wryyTxvha7TmfAHd7GRXrr2TaAn2HXn9T8ep4gyNX1bzGiieqcTUNcu2poyntrET"
}
}

View file

@ -1,97 +0,0 @@
# 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