From 6f50c200ee67f83aa225020516e6e2126f60a164 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 3 Jul 2015 11:28:06 +0200 Subject: [PATCH 1/3] Added import/export to the demo app - discovered private key length was wrong - fixed - as things are broken anyway, refactored flyway migrations - you'll need to delete ~/jabit.*.db --- demo/build.gradle | 1 + .../java/ch/dissem/bitmessage/demo/Main.java | 46 ++++++++++++++++++- .../bitmessage/entity/payload/CryptoBox.java | 5 +- .../entity/valueobject/PrivateKey.java | 9 ++-- .../db/migration/V1.0__Create_node_table.sql | 9 ---- ...e.sql => V1.0__Create_table_inventory.sql} | 0 ...ble.sql => V1.1__Create_table_address.sql} | 0 ...ble.sql => V1.2__Create_table_message.sql} | 2 +- .../db/migration/V2.0__Update_label.sql | 1 - .../db/migration/V3.0__Drop_node_table.sql | 1 - .../ch/dissem/bitmessage/wif/WifExporter.java | 24 ++++++---- .../ch/dissem/bitmessage/wif/WifImporter.java | 9 ++-- .../bitmessage/wif/WifImporterTest.java | 2 + 13 files changed, 80 insertions(+), 29 deletions(-) delete mode 100644 repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql rename repositories/src/main/resources/db/migration/{V1.1__Create_inventory_table.sql => V1.0__Create_table_inventory.sql} (100%) rename repositories/src/main/resources/db/migration/{V1.2__Create_address_table.sql => V1.1__Create_table_address.sql} (100%) rename repositories/src/main/resources/db/migration/{V1.3__Create_message_table.sql => V1.2__Create_table_message.sql} (98%) delete mode 100644 repositories/src/main/resources/db/migration/V2.0__Update_label.sql delete mode 100644 repositories/src/main/resources/db/migration/V3.0__Drop_node_table.sql diff --git a/demo/build.gradle b/demo/build.gradle index 3881faf..8d6414c 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -22,6 +22,7 @@ dependencies { compile project(':domain') compile project(':networking') compile project(':repositories') + compile project(':wif') compile 'org.slf4j:slf4j-simple:1.7.12' compile 'args4j:args4j:2.32' testCompile 'junit:junit:4.11' diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index cf29110..4ba8fa7 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -16,6 +16,16 @@ package ch.dissem.bitmessage.demo; +import ch.dissem.bitmessage.BitmessageContext; +import ch.dissem.bitmessage.networking.NetworkNode; +import ch.dissem.bitmessage.repository.*; +import ch.dissem.bitmessage.wif.WifExporter; +import ch.dissem.bitmessage.wif.WifImporter; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.Option; + +import java.io.File; import java.io.IOException; public class Main { @@ -25,6 +35,40 @@ public class Main { if (System.getProperty("org.slf4j.simpleLogger.logFile") == null) System.setProperty("org.slf4j.simpleLogger.logFile", "./jabit.log"); - new Application(); + CmdLineOptions options = new CmdLineOptions(); + CmdLineParser parser = new CmdLineParser(options); + try { + parser.parseArgument(args); + } catch (CmdLineException e) { + parser.printUsage(System.err); + } + if (options.exportWIF != null || options.importWIF != null) { + JdbcConfig jdbcConfig = new JdbcConfig(); + BitmessageContext ctx = new BitmessageContext.Builder() + .addressRepo(new JdbcAddressRepository(jdbcConfig)) + .inventory(new JdbcInventory(jdbcConfig)) + .nodeRegistry(new MemoryNodeRegistry()) + .messageRepo(new JdbcMessageRepository(jdbcConfig)) + .networkHandler(new NetworkNode()) + .port(48444) + .build(); + + if (options.exportWIF != null) { + new WifExporter(ctx).addAll().write(options.exportWIF); + } + if (options.importWIF != null) { + new WifImporter(ctx, options.importWIF).importAll(); + } + } else { + new Application(); + } + } + + private static class CmdLineOptions { + @Option(name = "-import", usage = "Import from keys.dat or other WIF file.") + private File importWIF; + + @Option(name = "-export", usage = "Export to WIF file.") + private File exportWIF; } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java index 5fc3990..2018373 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.entity.Streamable; +import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.utils.*; import org.bouncycastle.crypto.BufferedBlockCipher; @@ -37,6 +38,8 @@ import java.io.*; import java.math.BigInteger; import java.util.Arrays; +import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; + public class CryptoBox implements Streamable { private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class); @@ -59,7 +62,7 @@ public class CryptoBox implements Streamable { initializationVector = Security.randomBytes(16); // 3. Generate a new random EC key pair with private key called r and public key called R. - byte[] r = Security.randomBytes(64); + byte[] r = Security.randomBytes(PRIVATE_KEY_SIZE); R = Security.createPublicKey(r); // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. ECPoint P = K.multiply(Security.keyToBigInt(r)).normalize(); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java index d2482a1..f0956a6 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java @@ -32,8 +32,9 @@ import java.io.*; * Created by chris on 18.04.15. */ public class PrivateKey implements Streamable { - private final byte[] privateSigningKey; // 32 bytes - private final byte[] privateEncryptionKey; // 32 bytes + public static final int PRIVATE_KEY_SIZE = 32; + private final byte[] privateSigningKey; + private final byte[] privateEncryptionKey; private final Pubkey pubkey; @@ -44,8 +45,8 @@ public class PrivateKey implements Streamable { byte[] pubEK; byte[] ripe; do { - privSK = Security.randomBytes(64); - privEK = Security.randomBytes(64); + privSK = Security.randomBytes(PRIVATE_KEY_SIZE); + privEK = Security.randomBytes(PRIVATE_KEY_SIZE); pubSK = Security.createPublicKey(privSK).getEncoded(false); pubEK = Security.createPublicKey(privEK).getEncoded(false); ripe = Pubkey.getRipe(pubSK, pubEK); diff --git a/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql b/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql deleted file mode 100644 index 2472634..0000000 --- a/repositories/src/main/resources/db/migration/V1.0__Create_node_table.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE Node ( - ip BINARY(16) NOT NULL, - port INT NOT NULL, - stream BIGINT NOT NULL, - services BIGINT NOT NULL, - time BIGINT NOT NULL, - - PRIMARY KEY (ip, port, stream) -); \ No newline at end of file diff --git a/repositories/src/main/resources/db/migration/V1.1__Create_inventory_table.sql b/repositories/src/main/resources/db/migration/V1.0__Create_table_inventory.sql similarity index 100% rename from repositories/src/main/resources/db/migration/V1.1__Create_inventory_table.sql rename to repositories/src/main/resources/db/migration/V1.0__Create_table_inventory.sql diff --git a/repositories/src/main/resources/db/migration/V1.2__Create_address_table.sql b/repositories/src/main/resources/db/migration/V1.1__Create_table_address.sql similarity index 100% rename from repositories/src/main/resources/db/migration/V1.2__Create_address_table.sql rename to repositories/src/main/resources/db/migration/V1.1__Create_table_address.sql diff --git a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql b/repositories/src/main/resources/db/migration/V1.2__Create_table_message.sql similarity index 98% rename from repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql rename to repositories/src/main/resources/db/migration/V1.2__Create_table_message.sql index efd0294..5bdb0c3 100644 --- a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql +++ b/repositories/src/main/resources/db/migration/V1.2__Create_table_message.sql @@ -34,7 +34,7 @@ CREATE TABLE Message_Label ( ); INSERT INTO Label(label, type, color, ord) VALUES ('Inbox', 'INBOX', X'FF0000FF', 0); -INSERT INTO Label(label, type, color, ord) VALUES ('Drafts', 'DRAFTS', X'FFFF9900', 10); +INSERT INTO Label(label, type, color, ord) VALUES ('Drafts', 'DRAFT', X'FFFF9900', 10); INSERT INTO Label(label, type, color, ord) VALUES ('Sent', 'SENT', X'FFFFFF00', 20); INSERT INTO Label(label, type, ord) VALUES ('Unread', 'UNREAD', 90); INSERT INTO Label(label, type, ord) VALUES ('Trash', 'TRASH', 100); diff --git a/repositories/src/main/resources/db/migration/V2.0__Update_label.sql b/repositories/src/main/resources/db/migration/V2.0__Update_label.sql deleted file mode 100644 index 5712303..0000000 --- a/repositories/src/main/resources/db/migration/V2.0__Update_label.sql +++ /dev/null @@ -1 +0,0 @@ -UPDATE Label SET type = 'DRAFT' WHERE type = 'DRAFTS'; \ No newline at end of file diff --git a/repositories/src/main/resources/db/migration/V3.0__Drop_node_table.sql b/repositories/src/main/resources/db/migration/V3.0__Drop_node_table.sql deleted file mode 100644 index 7b43b4f..0000000 --- a/repositories/src/main/resources/db/migration/V3.0__Drop_node_table.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE Node; \ No newline at end of file diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java index 48d6bf6..26b6d63 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java @@ -26,6 +26,8 @@ import org.ini4j.Profile; import java.io.*; import java.util.Collection; +import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; + /** * @author Christian Basler */ @@ -38,19 +40,21 @@ public class WifExporter { this.ini = new Ini(); } - public void addAll() { + public WifExporter addAll() { for (BitmessageAddress identity : ctx.addresses().getIdentities()) { addIdentity(identity); } + return this; } - public void addAll(Collection identities) { + public WifExporter addAll(Collection identities) { for (BitmessageAddress identity : identities) { addIdentity(identity); } + return this; } - public void addIdentity(BitmessageAddress identity) { + public WifExporter addIdentity(BitmessageAddress identity) { Profile.Section section = ini.add(identity.getAddress()); section.add("label", identity.getAlias()); section.add("enabled", true); @@ -59,22 +63,26 @@ public class WifExporter { section.add("payloadlengthextrabytes", identity.getPubkey().getExtraBytes()); section.add("privsigningkey", exportSecret(identity.getPrivateKey().getPrivateSigningKey())); section.add("privencryptionkey", exportSecret(identity.getPrivateKey().getPrivateEncryptionKey())); + return this; } private String exportSecret(byte[] privateKey) { - if (privateKey.length != 32) { + if (privateKey.length != PRIVATE_KEY_SIZE) { throw new IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.length); } byte[] result = new byte[37]; result[0] = (byte) 0x80; - System.arraycopy(privateKey, 0, result, 1, 32); - byte[] hash = Security.doubleSha256(result, 33); - System.arraycopy(hash, 0, result, 33, 4); + System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE); + byte[] hash = Security.doubleSha256(result, PRIVATE_KEY_SIZE + 1); + System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4); return Base58.encode(result); } public void write(File file) throws IOException { - write(new FileOutputStream(file)); + file.createNewFile(); + try (FileOutputStream out = new FileOutputStream(file)) { + write(out); + } } public void write(OutputStream out) throws IOException { diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java index bfb0c13..45bbb4d 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java @@ -95,19 +95,22 @@ public class WifImporter { return identities; } - public void importAll() { + public WifImporter importAll() { for (BitmessageAddress identity : identities) { ctx.addresses().save(identity); } + return this; } - public void importAll(Collection identities) { + public WifImporter importAll(Collection identities) { for (BitmessageAddress identity : identities) { ctx.addresses().save(identity); } + return this; } - public void importIdentity(BitmessageAddress identity) { + public WifImporter importIdentity(BitmessageAddress identity) { ctx.addresses().save(identity); + return this; } } diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java index 0a1788f..2973efc 100644 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java @@ -64,6 +64,8 @@ public class WifImporterTest { assertEquals(320, identity.getPubkey().getNonceTrialsPerByte()); assertEquals(14000, identity.getPubkey().getExtraBytes()); assertNotNull("Private key", identity.getPrivateKey()); + assertEquals(32, identity.getPrivateKey().getPrivateEncryptionKey().length); + assertEquals(32, identity.getPrivateKey().getPrivateSigningKey().length); } @Test From 6fbb3f850d2fa6febdcf6c5d7acab4ea509e527f Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 3 Jul 2015 14:46:55 +0200 Subject: [PATCH 2/3] There are some instances where an inventory item is requested and therefore saved twice. This shouldn't be logged as an error. --- .../java/ch/dissem/bitmessage/repository/JdbcInventory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java index f499d6b..c7e8873 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java @@ -131,7 +131,7 @@ public class JdbcInventory extends JdbcHelper implements Inventory { ps.setLong(6, object.getVersion()); ps.executeUpdate(); } catch (SQLException e) { - LOG.error("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e); + LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e); } catch (Exception e) { LOG.error(e.getMessage(), e); } From c1ca1d856d380142aef4d56e1a77f1a56a71cbdf Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 3 Jul 2015 14:47:25 +0200 Subject: [PATCH 3/3] Version 0.2.0 bump --- README.md | 59 ++++++++++++++++++++++++++++++++++++++- build.gradle | 79 +++++++++++++++++++++++++++------------------------- 2 files changed, 99 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 794d8d5..eb5cdfc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Jabit [![Build Status](https://travis-ci.org/Dissem/Jabit.svg?branch=master)](https://travis-ci.org/Dissem/Jabit) ===== -A Java implementation for the Bitmessage protocol. To build, use command `gradle build`. Note that for some tests to run, a standard Bitmessage client needs to run on the same system, using port 8444 (the default port). +A Java implementation for the Bitmessage protocol. To build, use command `gradle build` or `./gradlew build`. Please note that development is still heavily in progress, and I will break the database a lot until it's ready for prime time. @@ -12,6 +12,7 @@ There are most probably some security issues, me programming this thing all by m Project Status -------------- + Basically, everything needed for a working Bitmessage client is there: * Creating new identities (private addresses) * Adding contracts and subscriptions @@ -22,3 +23,59 @@ Basically, everything needed for a working Bitmessage client is there: * Initialise and manage a registry of Bitmessage network nodes * An easy to use API * A command line demo application built using the API + +Setup +----- + +Add Jabit as Gradle dependency: +```Gradle +compile 'ch.dissem.jabit:jabit-domain:0.2.0' +``` +Unless you want to implement your own, also add the following: +```Gradle +compile 'ch.dissem.jabit:jabit-networking:0.2.0' +compile 'ch.dissem.jabit:jabit-repositories:0.2.0' +``` +And if you want to import from or export to the Wallet Import Format (used by PyBitmessage) you might also want to add: +```Gradle +compile 'ch.dissem.jabit:jabit-wif:0.2.0' +``` + +Usage +----- + +First, you'll need to create a `BitmessageContext`: +```Java +JdbcConfig jdbcConfig = new JdbcConfig(); +BitmessageContext ctx = new BitmessageContext.Builder() + .addressRepo(new JdbcAddressRepository(jdbcConfig)) + .inventory(new JdbcInventory(jdbcConfig)) + .messageRepo(new JdbcMessageRepository(jdbcConfig)) + .nodeRegistry(new MemoryNodeRegistry()) + .networkHandler(new NetworkNode()) + .build(); +``` +This creates a simple context using a H2 database that will be created in the user's home directory. Next you'll need to +start the context and decide what happens if a message arrives: +```Java +ctx.startup(new BitmessageContext.Listener() { + @Override + public void receive(Plaintext plaintext) { + // TODO: Notify the user + } +}); +``` +Then you might want to create an identity +```Java +BitmessageAddress identity = ctx.createIdentity(false, Pubkey.Feature.DOES_ACK); +``` +or add some contacts +```Java +BitmessageAddress contact = new BitmessageAddress("BM-2cTarrmjMdRicKZ4qQ8A13JhoR3Uq6Zh5j"); +address.setAlias("Chris"); +ctx.addContact(contact); +``` +to which you can send some messages +```Java +ctx.send(identity, contact, "Test", "Hello Chris, this is a message."); +``` diff --git a/build.gradle b/build.gradle index e344836..7bf5c3f 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,9 @@ subprojects { sourceCompatibility = 1.7 group = 'ch.dissem.jabit' - version = '0.1.3-SNAPSHOT' + version = '0.2.0' + + ext.isReleaseVersion = !version.endsWith("SNAPSHOT") repositories { mavenCentral() @@ -25,50 +27,51 @@ subprojects { archives javadocJar, sourcesJar } - // Note: to build the project, you'll either need to - if (hasProperty('signing.keyId')) { - signing { - sign configurations.archives - } + signing { + required { isReleaseVersion && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives } - if (hasProperty('ossrhUsername')) { - uploadArchives { - repositories { - mavenDeployer { - beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } + uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } - repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { - authentication(userName: ossrhUsername, password: ossrhPassword) + if (!hasProperty('ossrhUsername')) { + ext.ossrhUsername = 'dummy' + ext.ossrhPassword = 'dummy' + } + + repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { + authentication(userName: ossrhUsername, password: ossrhPassword) + } + + pom.project { + name 'Jabit' + packaging 'jar' + url 'https://github.com/Dissem/Jabit' + + scm { + connection 'scm:git:https://github.com/Dissem/Jabit.git' + developerConnection 'scm:git:git@github.com:Dissem/Jabit.git' + url 'https://github.com/Dissem/Jabit.git' } - snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { - authentication(userName: ossrhUsername, password: ossrhPassword) + licenses { + license { + name 'The Apache License, Version 2.0' + url 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } } - pom.project { - name 'Jabit' - packaging 'jar' - url 'https://github.com/Dissem/Jabit' - - scm { - connection 'scm:git:https://github.com/Dissem/Jabit.git' - developerConnection 'scm:git:git@github.com:Dissem/Jabit.git' - url 'https://github.com/Dissem/Jabit.git' - } - - licenses { - license { - name 'The Apache License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - - developers { - developer { - name 'Christian Basler' - email 'chrigu.meyer@gmail.com' - } + developers { + developer { + name 'Christian Basler' + email 'chrigu.meyer@gmail.com' } } }