add file import of the xpub descriptor jade writes to usb storage

This commit is contained in:
Craig Raw
2026-09-15 15:03:30 +02:00
parent 0e2c402f81
commit c4b53879cf
4 changed files with 74 additions and 4 deletions
+1 -1
Submodule drongo updated: a19eb5d9d4...340e23fcb0
@@ -1,12 +1,18 @@
package com.sparrowwallet.sparrow.io;
import com.google.common.io.CharStreams;
import com.sparrowwallet.drongo.OutputDescriptor;
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.nio.charset.StandardCharsets;
public class Jade implements KeystoreFileImport {
@Override
@@ -26,12 +32,35 @@ public class Jade implements KeystoreFileImport {
@Override
public Keystore getKeystore(PolicyType policyType, ScriptType scriptType, InputStream inputStream, String password) throws ImportException {
throw new ImportException("Failed to detect a valid " + scriptType.getDescription() + " keystore.");
try {
String text = CharStreams.toString(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).trim();
OutputDescriptor outputDescriptor = OutputDescriptor.getOutputDescriptor(text);
if(policyType == PolicyType.SINGLE_SP) {
throw new IllegalArgumentException("Export does not contain the spscan value for silent payments");
} else if(outputDescriptor.getScriptType() != scriptType) {
throw new IllegalArgumentException("The exported xpub is for " + outputDescriptor.getScriptType().getDescription() + ", not " + scriptType.getDescription()
+ ". Select " + scriptType.getDescription() + " in the options when using Export Xpub on your Jade, and export again.");
}
Wallet wallet = outputDescriptor.toWallet();
if(wallet.getKeystores().size() != 1) {
throw new IllegalArgumentException("Could not determine keystore from import");
}
Keystore keystore = wallet.getKeystores().getFirst();
keystore.setLabel(getName());
keystore.setWalletModel(getWalletModel());
keystore.setSource(KeystoreSource.HW_AIRGAPPED);
return keystore;
} catch(Exception e) {
throw new ImportException("Error getting " + getName() + " keystore", e);
}
}
@Override
public String getKeystoreImportDescription(int account) {
return "Import QR created on your Jade by selecting Options > Wallet > Export Xpub once you have loaded your seed. Make sure to select Singlesig as the Wallet type in the Options menu there.";
return "Import QR created on your Jade by selecting Options > Wallet > Export Xpub once you have loaded your seed, or the file written by Options > USB Storage > Export Xpub. Make sure to select Singlesig as the Wallet type in the Options menu there.";
}
@Override
@@ -41,6 +70,6 @@ public class Jade implements KeystoreFileImport {
@Override
public boolean isFileFormatAvailable() {
return false;
return true;
}
}
@@ -0,0 +1,40 @@
package com.sparrowwallet.sparrow.io;
import com.sparrowwallet.drongo.ExtendedKey;
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.WalletModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class JadeTest extends IoTest {
@Test
public void testImport() throws ImportException {
Jade jade = new Jade();
Keystore keystore = jade.getKeystore(PolicyType.SINGLE_HD, ScriptType.P2WPKH, getInputStream("jade-keystore.txt"), null);
Assertions.assertEquals("Jade", keystore.getLabel());
Assertions.assertEquals(WalletModel.JADE, keystore.getWalletModel());
Assertions.assertEquals(KeystoreSource.HW_AIRGAPPED, keystore.getSource());
Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath());
Assertions.assertEquals("73c5da0a", keystore.getKeyDerivation().getMasterFingerprint());
Assertions.assertEquals(ExtendedKey.fromDescriptor("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs"), keystore.getExtendedPublicKey());
Assertions.assertTrue(keystore.isValid());
}
@Test
public void testImportScriptTypeMismatch() {
Jade jade = new Jade();
ImportException scriptTypeException = Assertions.assertThrows(ImportException.class, () -> jade.getKeystore(PolicyType.SINGLE_HD, ScriptType.P2TR, getInputStream("jade-keystore.txt"), null));
Assertions.assertTrue(scriptTypeException.getCause().getMessage().startsWith("The exported xpub is for Native Segwit (P2WPKH), not Taproot (P2TR)."));
}
@Test
public void testImportSilentPayments() {
Jade jade = new Jade();
ImportException silentPaymentsException = Assertions.assertThrows(ImportException.class, () -> jade.getKeystore(PolicyType.SINGLE_SP, ScriptType.P2TR, getInputStream("jade-keystore.txt"), null));
Assertions.assertEquals("Export does not contain the spscan value for silent payments", silentPaymentsException.getCause().getMessage());
}
}
@@ -0,0 +1 @@
wpkh([73c5da0a/84'/0'/0']xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/0/*)#wc3n3van