From c4b53879cfbe5801f88fce007750de13c6e90100 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Tue, 15 Sep 2026 15:03:30 +0200 Subject: [PATCH] add file import of the xpub descriptor jade writes to usb storage --- drongo | 2 +- .../com/sparrowwallet/sparrow/io/Jade.java | 35 ++++++++++++++-- .../sparrowwallet/sparrow/io/JadeTest.java | 40 +++++++++++++++++++ .../sparrow/io/jade-keystore.txt | 1 + 4 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/test/java/com/sparrowwallet/sparrow/io/JadeTest.java create mode 100755 src/test/resources/com/sparrowwallet/sparrow/io/jade-keystore.txt diff --git a/drongo b/drongo index a19eb5d9..340e23fc 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit a19eb5d9d4322469e4e07cd19da3e182cbd56639 +Subproject commit 340e23fcb037b7839616001fff0b44ce4e51e800 diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Jade.java b/src/main/java/com/sparrowwallet/sparrow/io/Jade.java index 0905f111..bb43d759 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Jade.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Jade.java @@ -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; } } diff --git a/src/test/java/com/sparrowwallet/sparrow/io/JadeTest.java b/src/test/java/com/sparrowwallet/sparrow/io/JadeTest.java new file mode 100644 index 00000000..99b09371 --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/io/JadeTest.java @@ -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()); + } +} diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/jade-keystore.txt b/src/test/resources/com/sparrowwallet/sparrow/io/jade-keystore.txt new file mode 100755 index 00000000..8ef88fb6 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/jade-keystore.txt @@ -0,0 +1 @@ +wpkh([73c5da0a/84'/0'/0']xpub6CatWdiZiodmUeTDp8LT5or8nmbKNcuyvz7WyksVFkKB4RHwCD3XyuvPEbvqAQY3rAPshWcMLoP2fMFMKHPJ4ZeZXYVUhLv1VMrjPC7PW6V/0/*)#wc3n3van \ No newline at end of file