diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ECIESInputStream.java b/src/main/java/com/sparrowwallet/sparrow/io/ECIESInputStream.java new file mode 100644 index 00000000..377dfdad --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/ECIESInputStream.java @@ -0,0 +1,47 @@ +package com.sparrowwallet.sparrow.io; + +import com.google.common.io.ByteStreams; +import com.sparrowwallet.drongo.crypto.ECKey; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class ECIESInputStream extends FilterInputStream { + private boolean decrypted; + private final ECKey decryptionKey; + private final byte[] encryptionMagic; + + public ECIESInputStream(InputStream in, ECKey decryptionKey, byte[] encryptionMagic) { + super(in); + + if(in == null || decryptionKey == null || encryptionMagic == null) { + throw new NullPointerException(); + } + + this.decryptionKey = decryptionKey; + this.encryptionMagic = encryptionMagic; + } + + public ECIESInputStream(InputStream in, ECKey decryptionKey) { + this(in, decryptionKey, "BIE1".getBytes(StandardCharsets.UTF_8)); + } + + public int read() throws IOException { + ensureDecrypted(); + return super.read(); + } + + public int read(byte[] b, int off, int len) throws IOException { + ensureDecrypted(); + return super.read(b, off, len); + } + + private synchronized void ensureDecrypted() throws IOException { + if(!decrypted) { + byte[] encrypted = ByteStreams.toByteArray(in); + in.close(); + in = new ByteArrayInputStream(decryptionKey.decryptEcies(encrypted, encryptionMagic)); + decrypted = true; + } + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/ECIESOutputStream.java b/src/main/java/com/sparrowwallet/sparrow/io/ECIESOutputStream.java new file mode 100644 index 00000000..170c2c09 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/io/ECIESOutputStream.java @@ -0,0 +1,40 @@ +package com.sparrowwallet.sparrow.io; + +import com.google.common.io.ByteStreams; +import com.sparrowwallet.drongo.crypto.ECKey; + +import java.io.*; +import java.nio.charset.StandardCharsets; + +public class ECIESOutputStream extends FilterOutputStream { + private final ECKey encryptionKey; + private final byte[] encryptionMagic; + private final OutputStream encryptedStream; + + public ECIESOutputStream(OutputStream out, ECKey encryptionKey, byte[] encryptionMagic) { + super(new ByteArrayOutputStream()); + + encryptedStream = out; + + if(encryptionKey == null || encryptionMagic == null) { + throw new NullPointerException(); + } + + this.encryptionKey = encryptionKey; + this.encryptionMagic = encryptionMagic; + } + + public ECIESOutputStream(OutputStream in, ECKey encryptionKey) { + this(in, encryptionKey, "BIE1".getBytes(StandardCharsets.UTF_8)); + } + + @Override + public void close() throws IOException { + super.close(); + byte[] unencrypted = ((ByteArrayOutputStream)out).toByteArray(); + byte[] encryptedWallet = encryptionKey.encryptEcies(unencrypted, encryptionMagic); + ByteStreams.copy(new ByteArrayInputStream(encryptedWallet), encryptedStream); + encryptedStream.flush(); + encryptedStream.close(); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java index 5542709f..88869fa7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Electrum.java @@ -83,7 +83,7 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult ScriptType scriptType = null; for(ElectrumKeystore ek : ew.keystores.values()) { - Keystore keystore = new Keystore(ek.label); + Keystore keystore = new Keystore(ek.label != null ? ek.label : "Electrum " + ek.root_fingerprint); if("hardware".equals(ek.type)) { keystore.setSource(KeystoreSource.HW_USB); keystore.setWalletModel(WalletModel.fromType(ek.hw_type)); @@ -98,8 +98,9 @@ public class Electrum implements KeystoreFileImport, SinglesigWalletImport, Mult } keystore.setWalletModel(WalletModel.ELECTRUM); } + ExtendedPublicKey xPub = ExtendedPublicKey.fromDescriptor(ek.xpub); keystore.setKeyDerivation(new KeyDerivation(ek.root_fingerprint, ek.derivation)); - keystore.setExtendedPublicKey(ExtendedPublicKey.fromDescriptor(ek.xpub)); + keystore.setExtendedPublicKey(xPub); wallet.getKeystores().add(keystore); ExtendedPublicKey.XpubHeader xpubHeader = ExtendedPublicKey.XpubHeader.fromXpub(ek.xpub); diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ColdcardMultisigTest.java b/src/test/java/com/sparrowwallet/sparrow/io/ColdcardMultisigTest.java index 1e023745..1bc43241 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/ColdcardMultisigTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/ColdcardMultisigTest.java @@ -11,7 +11,7 @@ import org.junit.Test; import java.io.*; -public class ColdcardMultisigTest extends ImportExportTest { +public class ColdcardMultisigTest extends IoTest { @Test public void importKeystore1() throws ImportException { ColdcardMultisig ccMultisig = new ColdcardMultisig(); diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ColdcardSinglesigTest.java b/src/test/java/com/sparrowwallet/sparrow/io/ColdcardSinglesigTest.java index 5ffa16e0..dac6ea77 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/ColdcardSinglesigTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/ColdcardSinglesigTest.java @@ -6,7 +6,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import org.junit.Assert; import org.junit.Test; -public class ColdcardSinglesigTest extends ImportExportTest { +public class ColdcardSinglesigTest extends IoTest { @Test public void testImport() throws ImportException { ColdcardSinglesig ccSingleSig = new ColdcardSinglesig(); diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ECIESInputStreamTest.java b/src/test/java/com/sparrowwallet/sparrow/io/ECIESInputStreamTest.java new file mode 100644 index 00000000..b483e12d --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/io/ECIESInputStreamTest.java @@ -0,0 +1,28 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.crypto.ECKey; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.Assert; +import org.junit.Test; + +import java.util.zip.InflaterInputStream; + +public class ECIESInputStreamTest extends IoTest { + @Test + public void decrypt() throws ImportException { + Electrum electrum = new Electrum(); + ECKey decryptionKey = ECKey.createKeyPbkdf2HmacSha512("pass"); + Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey))); + + Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("pkh(electrum05aba071)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("05aba071", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub67vv394epQsLhdjNGx7dfgURicP7XwBMuHPTVAMdXcXhDuC9VP8SqVvh2cYqKWm9xoUd6YynWK8JzRcXpmeuZFRH7i1kt8fR9GXoJSiHk1E", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertTrue(wallet.isValid()); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ECIESOutputStreamTest.java b/src/test/java/com/sparrowwallet/sparrow/io/ECIESOutputStreamTest.java new file mode 100644 index 00000000..d31aa4bc --- /dev/null +++ b/src/test/java/com/sparrowwallet/sparrow/io/ECIESOutputStreamTest.java @@ -0,0 +1,38 @@ +package com.sparrowwallet.sparrow.io; + +import com.sparrowwallet.drongo.crypto.ECKey; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.wallet.Wallet; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +public class ECIESOutputStreamTest extends IoTest { + @Test + public void encrypt() throws ImportException, ExportException { + Electrum electrum = new Electrum(); + ECKey decryptionKey = ECKey.createKeyPbkdf2HmacSha512("pass"); + Wallet wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(getInputStream("electrum-encrypted"), decryptionKey))); + + ECKey encyptionKey = ECKey.fromPublicOnly(decryptionKey); + ByteArrayOutputStream dummyFileOutputStream = new ByteArrayOutputStream(); + electrum.exportWallet(wallet, new DeflaterOutputStream(new ECIESOutputStream(dummyFileOutputStream, encyptionKey))); + + ByteArrayInputStream dummyFileInputStream = new ByteArrayInputStream(dummyFileOutputStream.toByteArray()); + wallet = electrum.importWallet(new InflaterInputStream(new ECIESInputStream(dummyFileInputStream, decryptionKey))); + + Assert.assertEquals(PolicyType.SINGLE, wallet.getPolicyType()); + Assert.assertEquals(ScriptType.P2WPKH, wallet.getScriptType()); + Assert.assertEquals(1, wallet.getDefaultPolicy().getNumSignaturesRequired()); + Assert.assertEquals("pkh(electrum05aba071)", wallet.getDefaultPolicy().getMiniscript().getScript()); + Assert.assertEquals("05aba071", wallet.getKeystores().get(0).getKeyDerivation().getMasterFingerprint()); + Assert.assertEquals("m/0'", wallet.getKeystores().get(0).getKeyDerivation().getDerivationPath()); + Assert.assertEquals("xpub67vv394epQsLhdjNGx7dfgURicP7XwBMuHPTVAMdXcXhDuC9VP8SqVvh2cYqKWm9xoUd6YynWK8JzRcXpmeuZFRH7i1kt8fR9GXoJSiHk1E", wallet.getKeystores().get(0).getExtendedPublicKey().toString()); + Assert.assertTrue(wallet.isValid()); + } +} diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ElectrumTest.java b/src/test/java/com/sparrowwallet/sparrow/io/ElectrumTest.java index fc50b72d..195e444a 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/ElectrumTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/ElectrumTest.java @@ -11,7 +11,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -public class ElectrumTest extends ImportExportTest { +public class ElectrumTest extends IoTest { @Test public void testSinglesigImport() throws ImportException { Electrum electrum = new Electrum(); diff --git a/src/test/java/com/sparrowwallet/sparrow/io/ImportExportTest.java b/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java similarity index 87% rename from src/test/java/com/sparrowwallet/sparrow/io/ImportExportTest.java rename to src/test/java/com/sparrowwallet/sparrow/io/IoTest.java index d4e088f5..2aae742a 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/ImportExportTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java @@ -2,7 +2,7 @@ package com.sparrowwallet.sparrow.io; import java.io.InputStream; -public class ImportExportTest { +public class IoTest { protected InputStream getInputStream(String filename) { return this.getClass().getResourceAsStream("/com/sparrowwallet/sparrow/io/" + filename); diff --git a/src/test/resources/com/sparrowwallet/sparrow/io/electrum-encrypted b/src/test/resources/com/sparrowwallet/sparrow/io/electrum-encrypted new file mode 100644 index 00000000..a79d404a --- /dev/null +++ b/src/test/resources/com/sparrowwallet/sparrow/io/electrum-encrypted @@ -0,0 +1 @@ +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 \ No newline at end of file