Craig Raw
4 years ago
14 changed files with 236 additions and 11 deletions
@ -0,0 +1,70 @@ |
|||||
|
package com.sparrowwallet.sparrow.io; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.protocol.ScriptType; |
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import com.sparrowwallet.drongo.wallet.Wallet; |
||||
|
import com.sparrowwallet.drongo.wallet.WalletModel; |
||||
|
|
||||
|
import java.io.InputStream; |
||||
|
|
||||
|
public class KeystoneMultisig extends ColdcardMultisig { |
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "Keystone Multisig"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public WalletModel getWalletModel() { |
||||
|
return WalletModel.KEYSTONE; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { |
||||
|
Keystore keystore = super.getKeystore(scriptType, inputStream, password); |
||||
|
keystore.setLabel("Keystone"); |
||||
|
keystore.setWalletModel(getWalletModel()); |
||||
|
|
||||
|
return keystore; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getKeystoreImportDescription() { |
||||
|
return "Import file or QR created by using the Multisig Wallet > ... > Show/Export XPUB feature on your Keystone."; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException { |
||||
|
Wallet wallet = super.importWallet(inputStream, password); |
||||
|
for(Keystore keystore : wallet.getKeystores()) { |
||||
|
keystore.setLabel(keystore.getLabel().replace("Coldcard", "Keystone")); |
||||
|
keystore.setWalletModel(WalletModel.KEYSTONE); |
||||
|
} |
||||
|
|
||||
|
return wallet; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getWalletImportDescription() { |
||||
|
return "Import file or QR created by using the Multisig Wallet > ... > Create Multisig Wallet feature on your Keystone."; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getWalletExportDescription() { |
||||
|
return "Export file or QR that can be read by your Keystone using the Multisig Wallet > ... > Import Multisig Wallet feature."; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isWalletImportScannable() { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isKeystoreImportScannable() { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isWalletExportScannable() { |
||||
|
return true; |
||||
|
} |
||||
|
} |
@ -0,0 +1,109 @@ |
|||||
|
package com.sparrowwallet.sparrow.io; |
||||
|
|
||||
|
import com.google.gson.Gson; |
||||
|
import com.sparrowwallet.drongo.ExtendedKey; |
||||
|
import com.sparrowwallet.drongo.KeyDerivation; |
||||
|
import com.sparrowwallet.drongo.OutputDescriptor; |
||||
|
import com.sparrowwallet.drongo.policy.Policy; |
||||
|
import com.sparrowwallet.drongo.policy.PolicyType; |
||||
|
import com.sparrowwallet.drongo.protocol.ScriptType; |
||||
|
import com.sparrowwallet.drongo.wallet.*; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
|
||||
|
import java.io.BufferedReader; |
||||
|
import java.io.File; |
||||
|
import java.io.InputStream; |
||||
|
import java.io.InputStreamReader; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
public class KeystoneSinglesig implements KeystoreFileImport, WalletImport { |
||||
|
private static final Logger log = LoggerFactory.getLogger(KeystoneSinglesig.class); |
||||
|
|
||||
|
@Override |
||||
|
public String getName() { |
||||
|
return "Keystone"; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getKeystoreImportDescription() { |
||||
|
return "Import file or QR created by using the My Keystone > ... > Export Wallet feature on your Keystone. Make sure to set the Watch-only Wallet to Sparrow in the Settings first."; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public WalletModel getWalletModel() { |
||||
|
return WalletModel.KEYSTONE; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Keystore getKeystore(ScriptType scriptType, InputStream inputStream, String password) throws ImportException { |
||||
|
try { |
||||
|
String outputDescriptor = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.joining("\n")); |
||||
|
OutputDescriptor descriptor = OutputDescriptor.getOutputDescriptor(outputDescriptor); |
||||
|
|
||||
|
if(descriptor.isMultisig()) { |
||||
|
throw new IllegalArgumentException("Output descriptor describes a multisig wallet"); |
||||
|
} |
||||
|
|
||||
|
if(descriptor.getScriptType() != scriptType) { |
||||
|
throw new IllegalArgumentException("Output descriptor describes a " + descriptor.getScriptType().getDescription() + " wallet"); |
||||
|
} |
||||
|
|
||||
|
ExtendedKey xpub = descriptor.getSingletonExtendedPublicKey(); |
||||
|
KeyDerivation keyDerivation = descriptor.getKeyDerivation(xpub); |
||||
|
|
||||
|
Keystore keystore = new Keystore(); |
||||
|
keystore.setLabel(getName()); |
||||
|
keystore.setSource(KeystoreSource.HW_AIRGAPPED); |
||||
|
keystore.setWalletModel(WalletModel.KEYSTONE); |
||||
|
keystore.setKeyDerivation(keyDerivation); |
||||
|
keystore.setExtendedPublicKey(xpub); |
||||
|
|
||||
|
return keystore; |
||||
|
} catch (Exception e) { |
||||
|
log.error("Error getting Keystone keystore", e); |
||||
|
throw new ImportException("Error getting Keystone keystore", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getWalletImportDescription() { |
||||
|
return getKeystoreImportDescription(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Wallet importWallet(InputStream inputStream, String password) throws ImportException { |
||||
|
//Use default of P2WPKH
|
||||
|
Keystore keystore = getKeystore(ScriptType.P2WPKH, inputStream, ""); |
||||
|
|
||||
|
Wallet wallet = new Wallet(); |
||||
|
wallet.setPolicyType(PolicyType.SINGLE); |
||||
|
wallet.setScriptType(ScriptType.P2WPKH); |
||||
|
wallet.getKeystores().add(keystore); |
||||
|
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null)); |
||||
|
|
||||
|
try { |
||||
|
wallet.checkWallet(); |
||||
|
} catch(InvalidWalletException e) { |
||||
|
throw new ImportException("Imported Keystone wallet was invalid: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
return wallet; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isEncrypted(File file) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isWalletImportScannable() { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public boolean isKeystoreImportScannable() { |
||||
|
return true; |
||||
|
} |
||||
|
} |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,33 @@ |
|||||
|
package com.sparrowwallet.sparrow.io; |
||||
|
|
||||
|
import com.sparrowwallet.drongo.ExtendedKey; |
||||
|
import com.sparrowwallet.drongo.protocol.ScriptType; |
||||
|
import com.sparrowwallet.drongo.wallet.Keystore; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Test; |
||||
|
|
||||
|
public class KeystoneSinglesigTest extends IoTest { |
||||
|
@Test |
||||
|
public void testImport() throws ImportException { |
||||
|
KeystoneSinglesig keystoneSingleSig = new KeystoneSinglesig(); |
||||
|
Keystore keystore = keystoneSingleSig.getKeystore(ScriptType.P2WPKH, getInputStream("keystone-singlesig-keystore-1.txt"), null); |
||||
|
|
||||
|
Assert.assertEquals("Keystone", keystore.getLabel()); |
||||
|
Assert.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); |
||||
|
Assert.assertEquals("5271c071", keystore.getKeyDerivation().getMasterFingerprint()); |
||||
|
Assert.assertEquals(ExtendedKey.fromDescriptor("zpub6rcabYFcdr41zyUNRWRyHYs2Sm86E5XV8RjjRzTFYsiCngteeZnkwaF2xuhjmM6kpHjuNpFW42BMhzPmFwXt48e1FhddMB7xidZzN4SF24K"), keystore.getExtendedPublicKey()); |
||||
|
Assert.assertTrue(keystore.isValid()); |
||||
|
} |
||||
|
|
||||
|
@Test(expected = ImportException.class) |
||||
|
public void testIncorrectScriptType() throws ImportException { |
||||
|
KeystoneSinglesig keystoneSingleSig = new KeystoneSinglesig(); |
||||
|
Keystore keystore = keystoneSingleSig.getKeystore(ScriptType.P2SH_P2WPKH, getInputStream("keystone-singlesig-keystore-1.txt"), null); |
||||
|
|
||||
|
Assert.assertEquals("Keystone", keystore.getLabel()); |
||||
|
Assert.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); |
||||
|
Assert.assertEquals("5271c071", keystore.getKeyDerivation().getMasterFingerprint()); |
||||
|
Assert.assertEquals(ExtendedKey.fromDescriptor("zpub6rcabYFcdr41zyUNRWRyHYs2Sm86E5XV8RjjRzTFYsiCngteeZnkwaF2xuhjmM6kpHjuNpFW42BMhzPmFwXt48e1FhddMB7xidZzN4SF24K"), keystore.getExtendedPublicKey()); |
||||
|
Assert.assertTrue(keystore.isValid()); |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
wpkh([5271C071/84'/0'/0']zpub6rcabYFcdr41zyUNRWRyHYs2Sm86E5XV8RjjRzTFYsiCngteeZnkwaF2xuhjmM6kpHjuNpFW42BMhzPmFwXt48e1FhddMB7xidZzN4SF24K/1/*) |
Loading…
Reference in new issue