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