mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 08:37:05 +00:00
coldcard singlesig new feature, private key handling
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user