From 8b4f388eb703e03d4b230afb887d6e23747d6325 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 5 Mar 2026 09:39:33 +0200 Subject: [PATCH] add tests for descriptor import and export, and handle multiline descriptor --- .../sparrowwallet/sparrow/io/Descriptor.java | 16 +-- .../sparrow/io/DescriptorTest.java | 111 ++++++++++++++++++ .../sparrow/io/descriptor-labelled.txt | 1 + .../sparrow/io/descriptor-multipath.txt | 1 + .../sparrow/io/descriptor-receive-change1.txt | 3 + .../sparrow/io/descriptor-receive-change2.txt | 2 + .../sparrow/io/descriptor-receive.txt | 1 + 7 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 src/test/java/com/sparrowwallet/sparrow/io/DescriptorTest.java create mode 100644 src/test/resources/com/sparrowwallet/sparrow/io/descriptor-labelled.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/io/descriptor-multipath.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change1.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change2.txt create mode 100644 src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive.txt diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java index d3caae6d..ea8acb1b 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Descriptor.java @@ -114,26 +114,14 @@ public class Descriptor implements WalletImport, WalletExport { private static List getParagraphs(InputStream inputStream) { List paragraphs = new ArrayList<>(); - StringBuilder paragraph = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); for(String line : reader.lines().map(String::trim).toArray(String[]::new)) { - if(line.isEmpty()) { - if(!paragraph.isEmpty()) { - paragraphs.add(paragraph.toString()); - paragraph.setLength(0); - } - } else if(line.startsWith("#")) { - continue; - } else { - paragraph.append(line.replaceFirst("^.+:", "").trim()); + if(!line.isEmpty() && !line.startsWith("#")) { + paragraphs.add(line.replaceFirst("^.+:", "").trim()); } } - if(!paragraph.isEmpty()) { - paragraphs.add(paragraph.toString()); - } - return paragraphs; } diff --git a/src/test/java/com/sparrowwallet/sparrow/io/DescriptorTest.java b/src/test/java/com/sparrowwallet/sparrow/io/DescriptorTest.java new file mode 100644 index 00000000..9265a078 --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/io/DescriptorTest.java @@ -0,0 +1,111 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.ExtendedKey; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Keystore; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; + +public class DescriptorTest extends IoTest { + @Test + public void testImport() throws ImportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-receive.txt"), null); + + Assertions.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals("a262308d", keystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(ExtendedKey.fromDescriptor("xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz"), keystore.getExtendedPublicKey()); + Assertions.assertTrue(keystore.isValid()); + } + + @Test + public void testImportMultipath() throws ImportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-multipath.txt"), null); + + Assertions.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals("a262308d", keystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(ExtendedKey.fromDescriptor("xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz"), keystore.getExtendedPublicKey()); + Assertions.assertTrue(keystore.isValid()); + } + + @Test + public void testImportSeparateDescriptors() throws ImportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-receive-change1.txt"), null); + + Assertions.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals("a262308d", keystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(ExtendedKey.fromDescriptor("xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz"), keystore.getExtendedPublicKey()); + Assertions.assertTrue(keystore.isValid()); + } + + @Test + public void testExport() throws ImportException, ExportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-multipath.txt"), null); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + descriptor.exportWallet(wallet, baos, null); + String export = baos.toString(); + + Assertions.assertTrue(export.contains("wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/<0;1>/*)#cpx4ean7")); + Assertions.assertTrue(export.contains("wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/0/*)#s5x06kda")); + Assertions.assertTrue(export.contains("wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/1/*)#pqrw8ra9")); + } + + @Test + public void testImportExport() throws ImportException, ExportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-multipath.txt"), null); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + descriptor.exportWallet(wallet, baos, null); + + Wallet reimported = descriptor.importWallet(new ByteArrayInputStream(baos.toByteArray()), null); + + Assertions.assertEquals(wallet.getScriptType(), reimported.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Keystore reimportedKeystore = reimported.getKeystores().getFirst(); + Assertions.assertEquals(keystore.getKeyDerivation().getDerivationPath(), reimportedKeystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals(keystore.getKeyDerivation().getMasterFingerprint(), reimportedKeystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(keystore.getExtendedPublicKey(), reimportedKeystore.getExtendedPublicKey()); + Assertions.assertTrue(reimportedKeystore.isValid()); + } + + @Test + public void testImportLabelled() throws ImportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-labelled.txt"), null); + + Assertions.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals("a262308d", keystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(ExtendedKey.fromDescriptor("xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz"), keystore.getExtendedPublicKey()); + Assertions.assertTrue(keystore.isValid()); + } + + @Test + public void testImportSeparateDescriptorsNoBlankLine() throws ImportException { + Descriptor descriptor = new Descriptor(); + Wallet wallet = descriptor.importWallet(getInputStream("descriptor-receive-change2.txt"), null); + + Assertions.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Keystore keystore = wallet.getKeystores().getFirst(); + Assertions.assertEquals("m/84'/0'/0'", keystore.getKeyDerivation().getDerivationPath()); + Assertions.assertEquals("a262308d", keystore.getKeyDerivation().getMasterFingerprint()); + Assertions.assertEquals(ExtendedKey.fromDescriptor("xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz"), keystore.getExtendedPublicKey()); + Assertions.assertTrue(keystore.isValid()); + } +} diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-labelled.txt b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-labelled.txt new file mode 100644 index 00000000..2bdffd90 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-labelled.txt @@ -0,0 +1 @@ +Receive: wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/0/*)#s5x06kda diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-multipath.txt b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-multipath.txt new file mode 100644 index 00000000..6f5405ad --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-multipath.txt @@ -0,0 +1 @@ +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/<0;1>/*)#cpx4ean7 diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change1.txt b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change1.txt new file mode 100644 index 00000000..f96858ae --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change1.txt @@ -0,0 +1,3 @@ +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/0/*)#s5x06kda + +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/1/*)#pqrw8ra9 diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change2.txt b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change2.txt new file mode 100644 index 00000000..ff2a9c80 --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive-change2.txt @@ -0,0 +1,2 @@ +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/0/*)#s5x06kda +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/1/*)#pqrw8ra9 diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive.txt b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive.txt new file mode 100644 index 00000000..d98a0c7d --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/descriptor-receive.txt @@ -0,0 +1 @@ +wpkh([a262308d/84h/0h/0h]xpub6DM7CYgaTMdMbhTcLTUWmNUE5WLXK5hx8ZMa4sRw8qYJPqtqKYiKnwsmT8A6AijDVAUZRivdBnXdR8QE7Y9vVnqvzPL3fXCmu1WtCRLdAoz/0/*)#s5x06kda