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
@@ -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));
});
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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()) {
@@ -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();
}
@@ -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;
}
@@ -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());
}
}
}
@@ -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(