diff --git a/build.gradle b/build.gradle
index 78bf578..2169e2f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,22 +1,38 @@
+buildscript {
+    ext.kotlin_version = '1.1.3'
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
 plugins {
-    id 'com.github.ben-manes.versions' version '0.14.0'
+    id 'com.github.ben-manes.versions' version '0.15.0'
+    id "io.spring.dependency-management" version "1.0.3.RELEASE"
 }
 
 subprojects {
-    apply plugin: 'java'
+    apply plugin: 'kotlin'
     apply plugin: 'maven'
     apply plugin: 'signing'
     apply plugin: 'jacoco'
     apply plugin: 'gitflow-version'
+    apply plugin: 'io.spring.dependency-management'
     apply plugin: 'com.github.ben-manes.versions'
 
     sourceCompatibility = 1.7
+    targetCompatibility = 1.7
     group = 'ch.dissem.jabit'
 
     repositories {
         mavenCentral()
         maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
     }
+    dependencies {
+        compile "org.jetbrains.kotlin:kotlin-stdlib-jre7"
+        compile "org.jetbrains.kotlin:kotlin-reflect"
+    }
 
     test {
         testLogging {
@@ -38,6 +54,14 @@ subprojects {
         archives javadocJar, sourcesJar
     }
 
+    jar {
+        manifest {
+            attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}",
+                       'Implementation-Version': version
+        }
+        baseName "jabit-${project.name}"
+    }
+
     signing {
         required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 }
         sign configurations.archives
@@ -93,4 +117,31 @@ subprojects {
     }
 
     check.dependsOn jacocoTestReport
+
+    dependencyManagement {
+        dependencies {
+            dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") {
+                entry 'kotlin-stdlib-jre7'
+                entry 'kotlin-reflect'
+            }
+            dependencySet(group: 'org.slf4j', version: '1.7.25') {
+                entry 'slf4j-api'
+                entry 'slf4j-simple'
+            }
+
+            dependency 'ch.dissem.msgpack:msgpack:1.0.0'
+            dependency 'org.bouncycastle:bcprov-jdk15on:1.57'
+            dependency 'com.madgag.spongycastle:prov:1.56.0.0'
+            dependency 'org.apache.commons:commons-lang3:3.6'
+            dependency 'org.flywaydb:flyway-core:4.2.0'
+
+            dependency 'args4j:args4j:2.33'
+            dependency 'org.ini4j:ini4j:0.5.4'
+            dependency 'com.h2database:h2:1.4.196'
+
+            dependency 'junit:junit:4.12'
+            dependency 'org.hamcrest:hamcrest-library:1.3'
+            dependency 'com.nhaarman:mockito-kotlin:1.5.0'
+        }
+    }
 }
diff --git a/core/build.gradle b/core/build.gradle
index 785507e..6af7a84 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -24,10 +24,28 @@ artifacts {
 }
 
 dependencies {
-    compile 'org.slf4j:slf4j-api:1.7.25'
+    compile 'org.slf4j:slf4j-api'
     compile 'ch.dissem.msgpack:msgpack:1.0.0'
     testCompile 'junit:junit:4.12'
     testCompile 'org.hamcrest:hamcrest-library:1.3'
-    testCompile 'org.mockito:mockito-core:2.7.21'
+    testCompile 'com.nhaarman:mockito-kotlin:1.5.0'
     testCompile project(':cryptography-bc')
 }
+
+def generatedResources = "${project.buildDir}/generated-resources/main"
+
+sourceSets {
+    main {
+        output.dir(generatedResources, builtBy: 'generateVersionInfo')
+    }
+}
+task('generateVersionInfo') {
+    doLast {
+        def dir = new File(generatedResources)
+        if (!dir.exists()) {
+            dir.mkdirs()
+        }
+        def file = new File(generatedResources, "version")
+        file.write(project.version.toString())
+    }
+}
diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
deleted file mode 100644
index 29639e7..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage;
-
-import ch.dissem.bitmessage.entity.*;
-import ch.dissem.bitmessage.entity.payload.Broadcast;
-import ch.dissem.bitmessage.entity.payload.ObjectType;
-import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
-import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
-import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.factory.Factory;
-import ch.dissem.bitmessage.ports.*;
-import ch.dissem.bitmessage.utils.Property;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.net.InetAddress;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-
-import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
-import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
-import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
-import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
-import static ch.dissem.bitmessage.utils.UnixTime.HOUR;
-import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
-
-/**
- * 
Use this class if you want to create a Bitmessage client.
- * You'll need the Builder to create a BitmessageContext, and set the following properties:
- * 
- * - addressRepo
 
- * - inventory
 
- * - nodeRegistry
 
- * - networkHandler
 
- * - messageRepo
 
- * - streams
 
- * 
- * The default implementations in the different module builds can be used.
- * The port defaults to 8444 (the default Bitmessage port)
- */
-public class BitmessageContext {
-    public static final int CURRENT_VERSION = 3;
-    private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
-
-    private final InternalContext ctx;
-
-    private final Labeler labeler;
-
-    private final boolean sendPubkeyOnIdentityCreation;
-
-    private BitmessageContext(Builder builder) {
-        ctx = new InternalContext(builder);
-        labeler = builder.labeler;
-        ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
-        sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
-        if (builder.listener instanceof Listener.WithContext) {
-            ((Listener.WithContext) builder.listener).setContext(this);
-        }
-    }
-
-    public AddressRepository addresses() {
-        return ctx.getAddressRepository();
-    }
-
-    public MessageRepository messages() {
-        return ctx.getMessageRepository();
-    }
-
-    public Labeler labeler() {
-        return labeler;
-    }
-
-    public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
-        final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
-            shorter,
-            ctx.getStreams()[0],
-            NETWORK_NONCE_TRIALS_PER_BYTE,
-            NETWORK_EXTRA_BYTES,
-            features
-        ));
-        ctx.getAddressRepository().save(identity);
-        if (sendPubkeyOnIdentityCreation) {
-            ctx.sendPubkey(identity, identity.getStream());
-        }
-        return identity;
-    }
-
-    public BitmessageAddress joinChan(String passphrase, String address) {
-        BitmessageAddress chan = BitmessageAddress.chan(address, passphrase);
-        chan.setAlias(passphrase);
-        ctx.getAddressRepository().save(chan);
-        return chan;
-    }
-
-    public BitmessageAddress createChan(String passphrase) {
-        // FIXME: hardcoded stream number
-        BitmessageAddress chan = BitmessageAddress.chan(1, passphrase);
-        ctx.getAddressRepository().save(chan);
-        return chan;
-    }
-
-    public List createDeterministicAddresses(
-        String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
-        List result = BitmessageAddress.deterministic(
-            passphrase, numberOfAddresses, version, stream, shorter);
-        for (int i = 0; i < result.size(); i++) {
-            BitmessageAddress address = result.get(i);
-            address.setAlias("deterministic (" + (i + 1) + ")");
-            ctx.getAddressRepository().save(address);
-        }
-        return result;
-    }
-
-    public void broadcast(final BitmessageAddress from, final String subject, final String message) {
-        Plaintext msg = new Plaintext.Builder(BROADCAST)
-            .from(from)
-            .message(subject, message)
-            .build();
-        send(msg);
-    }
-
-    public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) {
-        if (from.getPrivateKey() == null) {
-            throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
-        }
-        Plaintext msg = new Plaintext.Builder(MSG)
-            .from(from)
-            .to(to)
-            .message(subject, message)
-            .build();
-        send(msg);
-    }
-
-    public void send(final Plaintext msg) {
-        if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
-            throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
-        }
-        labeler().markAsSending(msg);
-        BitmessageAddress to = msg.getTo();
-        if (to != null) {
-            if (to.getPubkey() == null) {
-                LOG.info("Public key is missing from recipient. Requesting.");
-                ctx.requestPubkey(to);
-            }
-            if (to.getPubkey() == null) {
-                ctx.getMessageRepository().save(msg);
-            }
-        }
-        if (to == null || to.getPubkey() != null) {
-            LOG.info("Sending message.");
-            ctx.getMessageRepository().save(msg);
-            if (msg.getType() == MSG) {
-                ctx.send(msg);
-            } else {
-                ctx.send(
-                    msg.getFrom(),
-                    to,
-                    Factory.getBroadcast(msg),
-                    msg.getTTL()
-                );
-            }
-        }
-    }
-
-    public void startup() {
-        ctx.getNetworkHandler().start();
-    }
-
-    public void shutdown() {
-        ctx.getNetworkHandler().stop();
-    }
-
-    /**
-     * @param host             a trusted node that must be reliable (it's used for every synchronization)
-     * @param port             of the trusted host, default is 8444
-     * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even
-     *                         if not all objects were fetched
-     * @param wait             waits for the synchronization thread to finish
-     */
-    public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) {
-        Future> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds);
-        if (wait) {
-            try {
-                future.get();
-            } catch (InterruptedException e) {
-                LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.");
-                future.cancel(true);
-            } catch (CancellationException | ExecutionException e) {
-                LOG.debug(e.getMessage(), e);
-            }
-        }
-    }
-
-    /**
-     * Send a custom message to a specific node (that should implement handling for this message type) and returns
-     * the response, which in turn is expected to be a {@link CustomMessage}.
-     *
-     * @param server  the node's address
-     * @param port    the node's port
-     * @param request the request
-     * @return the response
-     */
-    public CustomMessage send(InetAddress server, int port, CustomMessage request) {
-        return ctx.getNetworkHandler().send(server, port, request);
-    }
-
-    /**
-     * Removes expired objects from the inventory. You should call this method regularly,
-     * e.g. daily and on each shutdown.
-     */
-    public void cleanup() {
-        ctx.getInventory().cleanup();
-    }
-
-    /**
-     * Sends messages again whose time to live expired without being acknowledged. (And whose
-     * recipient is expected to send acknowledgements.
-     * 
-     * You should call this method regularly, but be aware of the following:
-     * 
-     * - As messages might be sent, POW will be done. It is therefore not advised to
-     * call it on shutdown.
 
-     * - It shouldn't be called right after startup, as it's possible the missing
-     * acknowledgement was sent while the client was offline.
 
-     * - Other than that, the call isn't expensive as long as there is no message
-     * to send, so it might be a good idea to just call it every few minutes.
 
-     * 
-     */
-    public void resendUnacknowledgedMessages() {
-        ctx.resendUnacknowledged();
-    }
-
-    public boolean isRunning() {
-        return ctx.getNetworkHandler().isRunning();
-    }
-
-    public void addContact(BitmessageAddress contact) {
-        ctx.getAddressRepository().save(contact);
-        if (contact.getPubkey() == null) {
-            BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress());
-            if (stored.getPubkey() == null) {
-                ctx.requestPubkey(contact);
-            }
-        }
-    }
-
-    public void addSubscribtion(BitmessageAddress address) {
-        address.setSubscribed(true);
-        ctx.getAddressRepository().save(address);
-        tryToFindBroadcastsForAddress(address);
-    }
-
-    private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
-        for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
-            try {
-                Broadcast broadcast = (Broadcast) object.getPayload();
-                broadcast.decrypt(address);
-                // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with
-                // other subscriptions and the interface stays as simple as possible.
-                ctx.getNetworkListener().receive(object);
-            } catch (DecryptionFailedException ignore) {
-            } catch (Exception e) {
-                LOG.debug(e.getMessage(), e);
-            }
-        }
-    }
-
-    public Property status() {
-        return new Property("status", null,
-            ctx.getNetworkHandler().getNetworkStatus(),
-            new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size())
-        );
-    }
-
-    /**
-     * Returns the {@link InternalContext} - normally you wouldn't need it,
-     * unless you are doing something crazy with the protocol.
-     */
-    public InternalContext internals() {
-        return ctx;
-    }
-
-    public interface Listener {
-        void receive(Plaintext plaintext);
-
-        /**
-         * A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot.
-         */
-        interface WithContext extends Listener {
-            void setContext(BitmessageContext ctx);
-        }
-    }
-
-    public static final class Builder {
-        int port = 8444;
-        Inventory inventory;
-        NodeRegistry nodeRegistry;
-        NetworkHandler networkHandler;
-        AddressRepository addressRepo;
-        MessageRepository messageRepo;
-        ProofOfWorkRepository proofOfWorkRepository;
-        ProofOfWorkEngine proofOfWorkEngine;
-        Cryptography cryptography;
-        CustomCommandHandler customCommandHandler;
-        Labeler labeler;
-        Listener listener;
-        int connectionLimit = 150;
-        long connectionTTL = 30 * MINUTE;
-        boolean sendPubkeyOnIdentityCreation = true;
-
-        public Builder port(int port) {
-            this.port = port;
-            return this;
-        }
-
-        public Builder inventory(Inventory inventory) {
-            this.inventory = inventory;
-            return this;
-        }
-
-        public Builder nodeRegistry(NodeRegistry nodeRegistry) {
-            this.nodeRegistry = nodeRegistry;
-            return this;
-        }
-
-        public Builder networkHandler(NetworkHandler networkHandler) {
-            this.networkHandler = networkHandler;
-            return this;
-        }
-
-        public Builder addressRepo(AddressRepository addressRepo) {
-            this.addressRepo = addressRepo;
-            return this;
-        }
-
-        public Builder messageRepo(MessageRepository messageRepo) {
-            this.messageRepo = messageRepo;
-            return this;
-        }
-
-        public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) {
-            this.proofOfWorkRepository = proofOfWorkRepository;
-            return this;
-        }
-
-        public Builder cryptography(Cryptography cryptography) {
-            this.cryptography = cryptography;
-            return this;
-        }
-
-        public Builder customCommandHandler(CustomCommandHandler handler) {
-            this.customCommandHandler = handler;
-            return this;
-        }
-
-        public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
-            this.proofOfWorkEngine = proofOfWorkEngine;
-            return this;
-        }
-
-        public Builder labeler(Labeler labeler) {
-            this.labeler = labeler;
-            return this;
-        }
-
-        public Builder listener(Listener listener) {
-            this.listener = listener;
-            return this;
-        }
-
-        public Builder connectionLimit(int connectionLimit) {
-            this.connectionLimit = connectionLimit;
-            return this;
-        }
-
-        public Builder connectionTTL(int hours) {
-            this.connectionTTL = hours * HOUR;
-            return this;
-        }
-
-        /**
-         * By default a client will send the public key when an identity is being created. On weaker devices
-         * this behaviour might not be desirable.
-         */
-        public Builder doNotSendPubkeyOnIdentityCreation() {
-            this.sendPubkeyOnIdentityCreation = false;
-            return this;
-        }
-
-        public BitmessageContext build() {
-            nonNull("inventory", inventory);
-            nonNull("nodeRegistry", nodeRegistry);
-            nonNull("networkHandler", networkHandler);
-            nonNull("addressRepo", addressRepo);
-            nonNull("messageRepo", messageRepo);
-            nonNull("proofOfWorkRepo", proofOfWorkRepository);
-            if (proofOfWorkEngine == null) {
-                proofOfWorkEngine = new MultiThreadedPOWEngine();
-            }
-            if (labeler == null) {
-                labeler = new DefaultLabeler();
-            }
-            if (customCommandHandler == null) {
-                customCommandHandler = new CustomCommandHandler() {
-                    @Override
-                    public MessagePayload handle(CustomMessage request) {
-                        LOG.debug("Received custom request, but no custom command handler configured.");
-                        return null;
-                    }
-                };
-            }
-            return new BitmessageContext(this);
-        }
-
-        private void nonNull(String name, Object o) {
-            if (o == null) throw new IllegalStateException(name + " must not be null");
-        }
-    }
-
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
deleted file mode 100644
index 4fb438f..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage;
-
-import ch.dissem.bitmessage.entity.BitmessageAddress;
-import ch.dissem.bitmessage.entity.ObjectMessage;
-import ch.dissem.bitmessage.entity.Plaintext;
-import ch.dissem.bitmessage.entity.payload.*;
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.ports.Labeler;
-import ch.dissem.bitmessage.ports.NetworkHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED;
-
-class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder {
-    private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class);
-    private final Labeler labeler;
-    private final BitmessageContext.Listener listener;
-    private InternalContext ctx;
-
-    public DefaultMessageListener(Labeler labeler, BitmessageContext.Listener listener) {
-        this.labeler = labeler;
-        this.listener = listener;
-    }
-
-    @Override
-    public void setContext(InternalContext context) {
-        this.ctx = context;
-    }
-
-    @Override
-    @SuppressWarnings("ConstantConditions")
-    public void receive(ObjectMessage object) throws IOException {
-        ObjectPayload payload = object.getPayload();
-        if (payload.getType() == null) {
-            if (payload instanceof GenericPayload) {
-                receive((GenericPayload) payload);
-            }
-            return;
-        }
-
-        switch (payload.getType()) {
-            case GET_PUBKEY: {
-                receive(object, (GetPubkey) payload);
-                break;
-            }
-            case PUBKEY: {
-                receive(object, (Pubkey) payload);
-                break;
-            }
-            case MSG: {
-                receive(object, (Msg) payload);
-                break;
-            }
-            case BROADCAST: {
-                receive(object, (Broadcast) payload);
-                break;
-            }
-            default: {
-                throw new IllegalArgumentException("Unknown payload type " + payload.getType());
-            }
-        }
-    }
-
-    protected void receive(ObjectMessage object, GetPubkey getPubkey) {
-        BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag());
-        if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) {
-            LOG.info("Got pubkey request for identity " + identity);
-            // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days
-            ctx.sendPubkey(identity, object.getStream());
-        }
-    }
-
-    protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException {
-        BitmessageAddress address;
-        try {
-            if (pubkey instanceof V4Pubkey) {
-                V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
-                address = ctx.getAddressRepository().findContact(v4Pubkey.getTag());
-                if (address != null) {
-                    v4Pubkey.decrypt(address.getPublicDecryptionKey());
-                }
-            } else {
-                address = ctx.getAddressRepository().findContact(pubkey.getRipe());
-            }
-            if (address != null && address.getPubkey() == null) {
-                updatePubkey(address, pubkey);
-            }
-        } catch (DecryptionFailedException ignore) {
-        }
-    }
-
-    private void updatePubkey(BitmessageAddress address, Pubkey pubkey) {
-        address.setPubkey(pubkey);
-        LOG.info("Got pubkey for contact " + address);
-        ctx.getAddressRepository().save(address);
-        List messages = ctx.getMessageRepository().findMessages(PUBKEY_REQUESTED, address);
-        LOG.info("Sending " + messages.size() + " messages for contact " + address);
-        for (Plaintext msg : messages) {
-            ctx.getLabeler().markAsSending(msg);
-            ctx.getMessageRepository().save(msg);
-            ctx.send(msg);
-        }
-    }
-
-    protected void receive(ObjectMessage object, Msg msg) throws IOException {
-        for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) {
-            try {
-                msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
-                Plaintext plaintext = msg.getPlaintext();
-                plaintext.setTo(identity);
-                if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) {
-                    LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
-                } else {
-                    receive(object.getInventoryVector(), plaintext);
-                }
-                break;
-            } catch (DecryptionFailedException ignore) {
-            }
-        }
-    }
-
-    protected void receive(GenericPayload ack) {
-        if (ack.getData().length == Msg.ACK_LENGTH) {
-            Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData());
-            if (msg != null) {
-                ctx.getLabeler().markAsAcknowledged(msg);
-                ctx.getMessageRepository().save(msg);
-            }
-        }
-    }
-
-    protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException {
-        byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null;
-        for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) {
-            if (tag != null && !Arrays.equals(tag, subscription.getTag())) {
-                continue;
-            }
-            try {
-                broadcast.decrypt(subscription.getPublicDecryptionKey());
-                if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) {
-                    LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring.");
-                } else {
-                    receive(object.getInventoryVector(), broadcast.getPlaintext());
-                }
-            } catch (DecryptionFailedException ignore) {
-            }
-        }
-    }
-
-    protected void receive(InventoryVector iv, Plaintext msg) {
-        BitmessageAddress contact = ctx.getAddressRepository().getAddress(msg.getFrom().getAddress());
-        if (contact != null && contact.getPubkey() == null) {
-            updatePubkey(contact, msg.getFrom().getPubkey());
-        }
-
-        msg.setInventoryVector(iv);
-        labeler.setLabels(msg);
-        ctx.getMessageRepository().save(msg);
-        listener.receive(msg);
-
-        if (msg.getType() == Plaintext.Type.MSG && msg.getTo().has(Pubkey.Feature.DOES_ACK)) {
-            ObjectMessage ack = msg.getAckMessage();
-            if (ack != null) {
-                ctx.getInventory().storeObject(ack);
-                ctx.getNetworkHandler().offer(ack.getInventoryVector());
-            }
-        }
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
deleted file mode 100644
index 007e8f2..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage;
-
-import ch.dissem.bitmessage.entity.*;
-import ch.dissem.bitmessage.entity.payload.*;
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.ports.*;
-import ch.dissem.bitmessage.utils.Singleton;
-import ch.dissem.bitmessage.utils.TTL;
-import ch.dissem.bitmessage.utils.UnixTime;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-import java.util.TreeSet;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * The internal context should normally only be used for port implementations. If you need it in your client
- * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
- * get extended.
- * 
- * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
- * 
- */
-public class InternalContext {
-    private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
-
-    public final static long NETWORK_NONCE_TRIALS_PER_BYTE = 1000;
-    public final static long NETWORK_EXTRA_BYTES = 1000;
-
-    private final Executor threadPool = Executors.newCachedThreadPool();
-
-    private final Cryptography cryptography;
-    private final Inventory inventory;
-    private final NodeRegistry nodeRegistry;
-    private final NetworkHandler networkHandler;
-    private final AddressRepository addressRepository;
-    private final MessageRepository messageRepository;
-    private final ProofOfWorkRepository proofOfWorkRepository;
-    private final ProofOfWorkEngine proofOfWorkEngine;
-    private final CustomCommandHandler customCommandHandler;
-    private final ProofOfWorkService proofOfWorkService;
-    private final Labeler labeler;
-    private final NetworkHandler.MessageListener networkListener;
-
-    private final TreeSet streams = new TreeSet<>();
-    private final int port;
-    private final long clientNonce;
-    private long connectionTTL;
-    private int connectionLimit;
-
-    public InternalContext(BitmessageContext.Builder builder) {
-        this.cryptography = builder.cryptography;
-        this.inventory = builder.inventory;
-        this.nodeRegistry = builder.nodeRegistry;
-        this.networkHandler = builder.networkHandler;
-        this.addressRepository = builder.addressRepo;
-        this.messageRepository = builder.messageRepo;
-        this.proofOfWorkRepository = builder.proofOfWorkRepository;
-        this.proofOfWorkService = new ProofOfWorkService();
-        this.proofOfWorkEngine = builder.proofOfWorkEngine;
-        this.clientNonce = cryptography.randomNonce();
-        this.customCommandHandler = builder.customCommandHandler;
-        this.port = builder.port;
-        this.connectionLimit = builder.connectionLimit;
-        this.connectionTTL = builder.connectionTTL;
-        this.labeler = builder.labeler;
-        this.networkListener = new DefaultMessageListener(labeler, builder.listener);
-
-        Singleton.initialize(cryptography);
-
-        // TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
-        for (BitmessageAddress address : addressRepository.getIdentities()) {
-            streams.add(address.getStream());
-        }
-        for (BitmessageAddress address : addressRepository.getSubscriptions()) {
-            streams.add(address.getStream());
-        }
-        if (streams.isEmpty()) {
-            streams.add(1L);
-        }
-
-        init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
-            proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler,
-            networkListener);
-        for (BitmessageAddress identity : addressRepository.getIdentities()) {
-            streams.add(identity.getStream());
-        }
-    }
-
-    private void init(Object... objects) {
-        for (Object o : objects) {
-            if (o instanceof ContextHolder) {
-                ((ContextHolder) o).setContext(this);
-            }
-        }
-    }
-
-    public Cryptography getCryptography() {
-        return cryptography;
-    }
-
-    public Inventory getInventory() {
-        return inventory;
-    }
-
-    public NodeRegistry getNodeRegistry() {
-        return nodeRegistry;
-    }
-
-    public NetworkHandler getNetworkHandler() {
-        return networkHandler;
-    }
-
-    public AddressRepository getAddressRepository() {
-        return addressRepository;
-    }
-
-    public MessageRepository getMessageRepository() {
-        return messageRepository;
-    }
-
-    public ProofOfWorkRepository getProofOfWorkRepository() {
-        return proofOfWorkRepository;
-    }
-
-    public ProofOfWorkEngine getProofOfWorkEngine() {
-        return proofOfWorkEngine;
-    }
-
-    public ProofOfWorkService getProofOfWorkService() {
-        return proofOfWorkService;
-    }
-
-    public Labeler getLabeler() {
-        return labeler;
-    }
-
-    public NetworkHandler.MessageListener getNetworkListener() {
-        return networkListener;
-    }
-
-    public long[] getStreams() {
-        long[] result = new long[streams.size()];
-        int i = 0;
-        for (long stream : streams) {
-            result[i++] = stream;
-        }
-        return result;
-    }
-
-    public int getPort() {
-        return port;
-    }
-
-    public void send(final Plaintext plaintext) {
-        if (plaintext.getAckMessage() != null) {
-            long expires = UnixTime.now(+plaintext.getTTL());
-            LOG.info("Expires at " + expires);
-            proofOfWorkService.doProofOfWorkWithAck(plaintext, expires);
-        } else {
-            send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL());
-        }
-    }
-
-    public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload,
-                     final long timeToLive) {
-        try {
-            final BitmessageAddress recipient = (to != null ? to : from);
-            long expires = UnixTime.now(+timeToLive);
-            LOG.info("Expires at " + expires);
-            final ObjectMessage object = new ObjectMessage.Builder()
-                .stream(recipient.getStream())
-                .expiresTime(expires)
-                .payload(payload)
-                .build();
-            if (object.isSigned()) {
-                object.sign(from.getPrivateKey());
-            }
-            if (payload instanceof Broadcast) {
-                ((Broadcast) payload).encrypt();
-            } else if (payload instanceof Encrypted) {
-                object.encrypt(recipient.getPubkey());
-            }
-            proofOfWorkService.doProofOfWork(to, object);
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
-        try {
-            long expires = UnixTime.now(TTL.pubkey());
-            LOG.info("Expires at " + expires);
-            final ObjectMessage response = new ObjectMessage.Builder()
-                .stream(targetStream)
-                .expiresTime(expires)
-                .payload(identity.getPubkey())
-                .build();
-            response.sign(identity.getPrivateKey());
-            response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey()));
-            // TODO: remember that the pubkey is just about to be sent, and on which stream!
-            proofOfWorkService.doProofOfWork(response);
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    /**
-     * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
-     * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
-     */
-    public void requestPubkey(final BitmessageAddress contact) {
-        threadPool.execute(new Runnable() {
-            @Override
-            public void run() {
-                BitmessageAddress stored = addressRepository.getAddress(contact.getAddress());
-
-                tryToFindMatchingPubkey(contact);
-                if (contact.getPubkey() != null) {
-                    if (stored != null) {
-                        stored.setPubkey(contact.getPubkey());
-                        addressRepository.save(stored);
-                    } else {
-                        addressRepository.save(contact);
-                    }
-                    return;
-                }
-
-                if (stored == null) {
-                    addressRepository.save(contact);
-                }
-
-                long expires = UnixTime.now(TTL.getpubkey());
-                LOG.info("Expires at " + expires);
-                final ObjectMessage request = new ObjectMessage.Builder()
-                    .stream(contact.getStream())
-                    .expiresTime(expires)
-                    .payload(new GetPubkey(contact))
-                    .build();
-                proofOfWorkService.doProofOfWork(request);
-            }
-        });
-    }
-
-    private void tryToFindMatchingPubkey(BitmessageAddress address) {
-        BitmessageAddress stored = addressRepository.getAddress(address.getAddress());
-        if (stored != null) {
-            address.setAlias(stored.getAlias());
-            address.setSubscribed(stored.isSubscribed());
-        }
-        for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) {
-            try {
-                Pubkey pubkey = (Pubkey) object.getPayload();
-                if (address.getVersion() == 4) {
-                    V4Pubkey v4Pubkey = (V4Pubkey) pubkey;
-                    if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) {
-                        v4Pubkey.decrypt(address.getPublicDecryptionKey());
-                        if (object.isSignatureValid(v4Pubkey)) {
-                            address.setPubkey(v4Pubkey);
-                            addressRepository.save(address);
-                            break;
-                        } else {
-                            LOG.info("Found pubkey for " + address + " but signature is invalid");
-                        }
-                    }
-                } else {
-                    if (Arrays.equals(pubkey.getRipe(), address.getRipe())) {
-                        address.setPubkey(pubkey);
-                        addressRepository.save(address);
-                        break;
-                    }
-                }
-            } catch (Exception e) {
-                LOG.debug(e.getMessage(), e);
-            }
-        }
-    }
-
-    public void resendUnacknowledged() {
-        List messages = messageRepository.findMessagesToResend();
-        for (Plaintext message : messages) {
-            send(message);
-            messageRepository.save(message);
-        }
-    }
-
-    public long getClientNonce() {
-        return clientNonce;
-    }
-
-    public long getConnectionTTL() {
-        return connectionTTL;
-    }
-
-    public int getConnectionLimit() {
-        return connectionLimit;
-    }
-
-    public CustomCommandHandler getCustomCommandHandler() {
-        return customCommandHandler;
-    }
-
-    public interface ContextHolder {
-        void setContext(InternalContext context);
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
deleted file mode 100644
index 14e7c8a..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package ch.dissem.bitmessage;
-
-import ch.dissem.bitmessage.entity.*;
-import ch.dissem.bitmessage.entity.payload.Msg;
-import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.ports.Cryptography;
-import ch.dissem.bitmessage.ports.MessageRepository;
-import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
-import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
-import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
-import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES;
-import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE;
-import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-
-/**
- * @author Christian Basler
- */
-public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
-    private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
-
-    private Cryptography cryptography;
-    private InternalContext ctx;
-    private ProofOfWorkRepository powRepo;
-    private MessageRepository messageRepo;
-
-    public void doMissingProofOfWork(long delayInMilliseconds) {
-        final List items = powRepo.getItems();
-        if (items.isEmpty()) return;
-
-        // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
-        new Timer().schedule(new TimerTask() {
-            @Override
-            public void run() {
-                LOG.info("Doing POW for " + items.size() + " tasks.");
-                for (byte[] initialHash : items) {
-                    Item item = powRepo.getItem(initialHash);
-                    cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes,
-                        ProofOfWorkService.this);
-                }
-            }
-        }, delayInMilliseconds);
-    }
-
-    public void doProofOfWork(ObjectMessage object) {
-        doProofOfWork(null, object);
-    }
-
-    public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
-        Pubkey pubkey = recipient == null ? null : recipient.getPubkey();
-
-        long nonceTrialsPerByte = pubkey == null ? NETWORK_NONCE_TRIALS_PER_BYTE : pubkey.getNonceTrialsPerByte();
-        long extraBytes = pubkey == null ? NETWORK_EXTRA_BYTES : pubkey.getExtraBytes();
-
-        powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
-        if (object.getPayload() instanceof PlaintextHolder) {
-            Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext();
-            plaintext.setInitialHash(cryptography.getInitialHash(object));
-            messageRepo.save(plaintext);
-        }
-        cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this);
-    }
-
-    public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) {
-        final ObjectMessage ack = plaintext.getAckMessage();
-        messageRepo.save(plaintext);
-        Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES,
-            expirationTime, plaintext);
-        powRepo.putObject(item);
-        cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this);
-    }
-
-    @Override
-    public void onNonceCalculated(byte[] initialHash, byte[] nonce) {
-        Item item = powRepo.getItem(initialHash);
-        if (item.message == null) {
-            ObjectMessage object = item.object;
-            object.setNonce(nonce);
-            Plaintext plaintext = messageRepo.getMessage(initialHash);
-            if (plaintext != null) {
-                plaintext.setInventoryVector(object.getInventoryVector());
-                plaintext.updateNextTry();
-                ctx.getLabeler().markAsSent(plaintext);
-                messageRepo.save(plaintext);
-            }
-            try {
-                ctx.getNetworkListener().receive(object);
-            } catch (IOException e) {
-                LOG.debug(e.getMessage(), e);
-            }
-            ctx.getInventory().storeObject(object);
-            ctx.getNetworkHandler().offer(object.getInventoryVector());
-        } else {
-            item.message.getAckMessage().setNonce(nonce);
-            final ObjectMessage object = new ObjectMessage.Builder()
-                .stream(item.message.getStream())
-                .expiresTime(item.expirationTime)
-                .payload(new Msg(item.message))
-                .build();
-            if (object.isSigned()) {
-                object.sign(item.message.getFrom().getPrivateKey());
-            }
-            if (object.getPayload() instanceof Encrypted) {
-                object.encrypt(item.message.getTo().getPubkey());
-            }
-            doProofOfWork(item.message.getTo(), object);
-        }
-        powRepo.removeObject(initialHash);
-    }
-
-    @Override
-    public void setContext(InternalContext ctx) {
-        this.ctx = ctx;
-        this.cryptography = cryptography();
-        this.powRepo = ctx.getProofOfWorkRepository();
-        this.messageRepo = ctx.getMessageRepository();
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
deleted file mode 100644
index 73d9995..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * The 'addr' command holds a list of known active Bitmessage nodes.
- */
-public class Addr implements MessagePayload {
-    private static final long serialVersionUID = -5117688017050138720L;
-
-    private final List addresses;
-
-    private Addr(Builder builder) {
-        addresses = builder.addresses;
-    }
-
-    @Override
-    public Command getCommand() {
-        return Command.ADDR;
-    }
-
-    public List getAddresses() {
-        return addresses;
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        Encode.varInt(addresses.size(), out);
-        for (NetworkAddress address : addresses) {
-            address.write(out);
-        }
-    }
-
-    @Override
-    public void write(ByteBuffer buffer) {
-        Encode.varInt(addresses.size(), buffer);
-        for (NetworkAddress address : addresses) {
-            address.write(buffer);
-        }
-    }
-
-    public static final class Builder {
-        private List addresses = new ArrayList<>();
-
-        public Builder addresses(Collection addresses){
-            this.addresses.addAll(addresses);
-            return this;
-        }
-
-        public Builder addAddress(final NetworkAddress address) {
-            this.addresses.add(address);
-            return this;
-        }
-
-        public Addr build() {
-            return new Addr(this);
-        }
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
deleted file mode 100644
index ce199c4..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
-import ch.dissem.bitmessage.entity.payload.V4Pubkey;
-import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.utils.AccessCounter;
-import ch.dissem.bitmessage.utils.Base58;
-import ch.dissem.bitmessage.utils.Bytes;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
-
-import static ch.dissem.bitmessage.utils.Decode.bytes;
-import static ch.dissem.bitmessage.utils.Decode.varInt;
-import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-
-/**
- * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
- * holding private keys.
- */
-public class BitmessageAddress implements Serializable {
-    private static final long serialVersionUID = 2386328540805994064L;
-
-    private final long version;
-    private final long stream;
-    private final byte[] ripe;
-    private final byte[] tag;
-    /**
-     * Used for V4 address encryption. It's easier to just create it regardless of address version.
-     */
-    private final byte[] publicDecryptionKey;
-
-    private String address;
-
-    private PrivateKey privateKey;
-    private Pubkey pubkey;
-
-    private String alias;
-    private boolean subscribed;
-    private boolean chan;
-
-    BitmessageAddress(long version, long stream, byte[] ripe) {
-        try {
-            this.version = version;
-            this.stream = stream;
-            this.ripe = ripe;
-
-            ByteArrayOutputStream os = new ByteArrayOutputStream();
-            Encode.varInt(version, os);
-            Encode.varInt(stream, os);
-            if (version < 4) {
-                byte[] checksum = cryptography().sha512(os.toByteArray(), ripe);
-                this.tag = null;
-                this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
-            } else {
-                // for tag and decryption key, the checksum has to be created with 0x00 padding
-                byte[] checksum = cryptography().doubleSha512(os.toByteArray(), ripe);
-                this.tag = Arrays.copyOfRange(checksum, 32, 64);
-                this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
-            }
-            // but for the address and its checksum they need to be stripped
-            int offset = Bytes.numberOfLeadingZeros(ripe);
-            os.write(ripe, offset, ripe.length - offset);
-            byte[] checksum = cryptography().doubleSha512(os.toByteArray());
-            os.write(checksum, 0, 4);
-            this.address = "BM-" + Base58.encode(os.toByteArray());
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public BitmessageAddress(Pubkey publicKey) {
-        this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe());
-        this.pubkey = publicKey;
-    }
-
-    public BitmessageAddress(String address, String passphrase) {
-        this(address);
-        this.privateKey = new PrivateKey(this, passphrase);
-        this.pubkey = this.privateKey.getPubkey();
-        if (!Arrays.equals(ripe, privateKey.getPubkey().getRipe())) {
-            throw new IllegalArgumentException("Wrong address or passphrase");
-        }
-    }
-
-    public static BitmessageAddress chan(String address, String passphrase) {
-        BitmessageAddress result = new BitmessageAddress(address, passphrase);
-        result.chan = true;
-        return result;
-    }
-
-    public static BitmessageAddress chan(long stream, String passphrase) {
-        PrivateKey privateKey = new PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase);
-        BitmessageAddress result = new BitmessageAddress(privateKey);
-        result.chan = true;
-        return result;
-    }
-
-    public static List deterministic(String passphrase, int numberOfAddresses,
-                                                        long version, long stream, boolean shorter) {
-        List result = new ArrayList<>(numberOfAddresses);
-        List privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter);
-        for (PrivateKey pk : privateKeys) {
-            result.add(new BitmessageAddress(pk));
-        }
-        return result;
-    }
-
-    public BitmessageAddress(PrivateKey privateKey) {
-        this(privateKey.getPubkey());
-        this.privateKey = privateKey;
-    }
-
-    public BitmessageAddress(String address) {
-        try {
-            this.address = address;
-            byte[] bytes = Base58.decode(address.substring(3));
-            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
-            AccessCounter counter = new AccessCounter();
-            this.version = varInt(in, counter);
-            this.stream = varInt(in, counter);
-            this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
-
-            // test checksum
-            byte[] checksum = cryptography().doubleSha512(bytes, bytes.length - 4);
-            byte[] expectedChecksum = bytes(in, 4);
-            for (int i = 0; i < 4; i++) {
-                if (expectedChecksum[i] != checksum[i])
-                    throw new IllegalArgumentException("Checksum of address failed");
-            }
-            if (version < 4) {
-                checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
-                this.tag = null;
-                this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
-            } else {
-                checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
-                this.tag = Arrays.copyOfRange(checksum, 32, 64);
-                this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
-            }
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public static byte[] calculateTag(long version, long stream, byte[] ripe) {
-        try {
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            Encode.varInt(version, out);
-            Encode.varInt(stream, out);
-            out.write(ripe);
-            return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64);
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public long getStream() {
-        return stream;
-    }
-
-    public long getVersion() {
-        return version;
-    }
-
-    public Pubkey getPubkey() {
-        return pubkey;
-    }
-
-    public void setPubkey(Pubkey pubkey) {
-        if (pubkey instanceof V4Pubkey) {
-            if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag()))
-                throw new IllegalArgumentException("Pubkey has incompatible tag");
-        }
-        if (!Arrays.equals(ripe, pubkey.getRipe()))
-            throw new IllegalArgumentException("Pubkey has incompatible ripe");
-        this.pubkey = pubkey;
-    }
-
-    /**
-     * @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts.
-     */
-    public byte[] getPublicDecryptionKey() {
-        return publicDecryptionKey;
-    }
-
-    public PrivateKey getPrivateKey() {
-        return privateKey;
-    }
-
-    public String getAddress() {
-        return address;
-    }
-
-    public String getAlias() {
-        return alias;
-    }
-
-    public void setAlias(String alias) {
-        this.alias = alias;
-    }
-
-    @Override
-    public String toString() {
-        return alias == null ? address : alias;
-    }
-
-    public byte[] getRipe() {
-        return ripe;
-    }
-
-    public byte[] getTag() {
-        return tag;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        BitmessageAddress address = (BitmessageAddress) o;
-        return Objects.equals(version, address.version) &&
-                Objects.equals(stream, address.stream) &&
-                Arrays.equals(ripe, address.ripe);
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(ripe);
-    }
-
-    public boolean isSubscribed() {
-        return subscribed;
-    }
-
-    public void setSubscribed(boolean subscribed) {
-        this.subscribed = subscribed;
-    }
-
-    public boolean isChan() {
-        return chan;
-    }
-
-    public void setChan(boolean chan) {
-        this.chan = chan;
-    }
-
-    public boolean has(Feature feature) {
-        if (pubkey == null || feature == null) {
-            return false;
-        }
-        return feature.isActive(pubkey.getBehaviorBitfield());
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
deleted file mode 100644
index 439f003..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.utils.AccessCounter;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.*;
-import java.nio.ByteBuffer;
-
-import static ch.dissem.bitmessage.utils.Decode.bytes;
-import static ch.dissem.bitmessage.utils.Decode.varString;
-
-/**
- * @author Christian Basler
- */
-public class CustomMessage implements MessagePayload {
-    private static final long serialVersionUID = -8932056829480326011L;
-
-    public static final String COMMAND_ERROR = "ERROR";
-
-    private final String command;
-    private final byte[] data;
-
-    public CustomMessage(String command) {
-        this.command = command;
-        this.data = null;
-    }
-
-    public CustomMessage(String command, byte[] data) {
-        this.command = command;
-        this.data = data;
-    }
-
-    public static CustomMessage read(InputStream in, int length) throws IOException {
-        AccessCounter counter = new AccessCounter();
-        return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
-    }
-
-    @Override
-    public Command getCommand() {
-        return Command.CUSTOM;
-    }
-
-    public String getCustomCommand() {
-        return command;
-    }
-
-    public byte[] getData() {
-        if (data != null) {
-            return data;
-        } else {
-            try {
-                ByteArrayOutputStream out = new ByteArrayOutputStream();
-                write(out);
-                return out.toByteArray();
-            } catch (IOException e) {
-                throw new ApplicationException(e);
-            }
-        }
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        if (data != null) {
-            Encode.varString(command, out);
-            out.write(data);
-        } else {
-            throw new ApplicationException("Tried to write custom message without data. " +
-                    "Programmer: did you forget to override #write()?");
-        }
-    }
-
-    @Override
-    public void write(ByteBuffer buffer) {
-        if (data != null) {
-            Encode.varString(command, buffer);
-            buffer.put(data);
-        } else {
-            throw new ApplicationException("Tried to write custom message without data. " +
-                    "Programmer: did you forget to override #write()?");
-        }
-    }
-
-    public boolean isError() {
-        return COMMAND_ERROR.equals(command);
-    }
-
-    public static CustomMessage error(String message) {
-        try {
-            return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8"));
-        } catch (UnsupportedEncodingException e) {
-            throw new ApplicationException(e);
-        }
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
deleted file mode 100644
index 7d14fa0..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * The 'getdata' command is used to request objects from a node.
- */
-public class GetData implements MessagePayload {
-    private static final long serialVersionUID = 1433878785969631061L;
-
-    public static final int MAX_INVENTORY_SIZE = 50_000;
-
-    List inventory;
-
-    private GetData(Builder builder) {
-        inventory = builder.inventory;
-    }
-
-    @Override
-    public Command getCommand() {
-        return Command.GETDATA;
-    }
-
-    public List getInventory() {
-        return inventory;
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        Encode.varInt(inventory.size(), out);
-        for (InventoryVector iv : inventory) {
-            iv.write(out);
-        }
-    }
-
-    @Override
-    public void write(ByteBuffer buffer) {
-        Encode.varInt(inventory.size(), buffer);
-        for (InventoryVector iv : inventory) {
-            iv.write(buffer);
-        }
-    }
-
-    public static final class Builder {
-        private List inventory = new LinkedList<>();
-
-        public Builder addInventoryVector(InventoryVector inventoryVector) {
-            this.inventory.add(inventoryVector);
-            return this;
-        }
-
-        public Builder inventory(List inventory) {
-            this.inventory = inventory;
-            return this;
-        }
-
-        public GetData build() {
-            return new GetData(this);
-        }
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
deleted file mode 100644
index 8d0f592..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
- */
-public class Inv implements MessagePayload {
-    private static final long serialVersionUID = 3662992522956947145L;
-
-    private List inventory;
-
-    private Inv(Builder builder) {
-        inventory = builder.inventory;
-    }
-
-    public List getInventory() {
-        return inventory;
-    }
-
-    @Override
-    public Command getCommand() {
-        return Command.INV;
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        Encode.varInt(inventory.size(), out);
-        for (InventoryVector iv : inventory) {
-            iv.write(out);
-        }
-    }
-
-    @Override
-    public void write(ByteBuffer buffer) {
-        Encode.varInt(inventory.size(), buffer);
-        for (InventoryVector iv : inventory) {
-            iv.write(buffer);
-        }
-    }
-
-    public static final class Builder {
-        private List inventory = new LinkedList<>();
-
-        public Builder addInventoryVector(InventoryVector inventoryVector) {
-            this.inventory.add(inventoryVector);
-            return this;
-        }
-
-        public Builder inventory(List inventory) {
-            this.inventory = inventory;
-            return this;
-        }
-
-        public Inv build() {
-            return new Inv(this);
-        }
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
deleted file mode 100644
index f27384e..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-import java.nio.ByteBuffer;
-import java.security.GeneralSecurityException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-
-import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-
-/**
- * A network message is exchanged between two nodes.
- */
-public class NetworkMessage implements Streamable {
-    private static final long serialVersionUID = 702708857104464809L;
-
-    /**
-     * Magic value indicating message origin network, and used to seek to next message when stream state is unknown
-     */
-    public final static int MAGIC = 0xE9BEB4D9;
-    public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array();
-
-    private final MessagePayload payload;
-
-    public NetworkMessage(MessagePayload payload) {
-        this.payload = payload;
-    }
-
-    /**
-     * First 4 bytes of sha512(payload)
-     */
-    private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
-        byte[] d = cryptography().sha512(bytes);
-        return new byte[]{d[0], d[1], d[2], d[3]};
-    }
-
-    /**
-     * The actual data, a message or an object. Not to be confused with objectPayload.
-     */
-    public MessagePayload getPayload() {
-        return payload;
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        // magic
-        Encode.int32(MAGIC, out);
-
-        // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
-        String command = payload.getCommand().name().toLowerCase();
-        out.write(command.getBytes("ASCII"));
-        for (int i = command.length(); i < 12; i++) {
-            out.write('\0');
-        }
-
-        byte[] payloadBytes = Encode.bytes(payload);
-
-        // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
-        // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
-        // larger than this.
-        Encode.int32(payloadBytes.length, out);
-
-        // checksum
-        try {
-            out.write(getChecksum(payloadBytes));
-        } catch (GeneralSecurityException e) {
-            throw new ApplicationException(e);
-        }
-
-        // message payload
-        out.write(payloadBytes);
-    }
-
-    /**
-     * A more efficient implementation of the write method, writing header data to the provided buffer and returning
-     * a new buffer containing the payload.
-     *
-     * @param headerBuffer where the header data is written to (24 bytes)
-     * @return a buffer containing the payload, ready to be read.
-     */
-    public ByteBuffer writeHeaderAndGetPayloadBuffer(ByteBuffer headerBuffer) {
-        return ByteBuffer.wrap(writeHeader(headerBuffer));
-    }
-
-    /**
-     * For improved memory efficiency, you should use {@link #writeHeaderAndGetPayloadBuffer(ByteBuffer)}
-     * and write the header buffer as well as the returned payload buffer into the channel.
-     *
-     * @param buffer where everything gets written to. Needs to be large enough for the whole message
-     *               to be written.
-     */
-    @Override
-    public void write(ByteBuffer buffer) {
-        byte[] payloadBytes = writeHeader(buffer);
-        buffer.put(payloadBytes);
-    }
-
-    private byte[] writeHeader(ByteBuffer out) {
-        // magic
-        Encode.int32(MAGIC, out);
-
-        // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
-        String command = payload.getCommand().name().toLowerCase();
-        try {
-            out.put(command.getBytes("ASCII"));
-        } catch (UnsupportedEncodingException e) {
-            throw new ApplicationException(e);
-        }
-        for (int i = command.length(); i < 12; i++) {
-            out.put((byte) 0);
-        }
-
-        byte[] payloadBytes = Encode.bytes(payload);
-
-        // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
-        // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
-        // larger than this.
-        Encode.int32(payloadBytes.length, out);
-
-        // checksum
-        try {
-            out.put(getChecksum(payloadBytes));
-        } catch (GeneralSecurityException e) {
-            throw new ApplicationException(e);
-        }
-
-        // message payload
-        return payloadBytes;
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
deleted file mode 100644
index 8e386f7..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java
+++ /dev/null
@@ -1,271 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.payload.ObjectPayload;
-import ch.dissem.bitmessage.entity.payload.ObjectType;
-import ch.dissem.bitmessage.entity.payload.Pubkey;
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.exception.DecryptionFailedException;
-import ch.dissem.bitmessage.utils.Bytes;
-import ch.dissem.bitmessage.utils.Encode;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.Objects;
-
-import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-
-/**
- * The 'object' command sends an object that is shared throughout the network.
- */
-public class ObjectMessage implements MessagePayload {
-    private static final long serialVersionUID = 2495752480120659139L;
-
-    private byte[] nonce;
-    private long expiresTime;
-    private long objectType;
-    /**
-     * The object's version
-     */
-    private long version;
-    private long stream;
-
-    private ObjectPayload payload;
-    private byte[] payloadBytes;
-
-    private ObjectMessage(Builder builder) {
-        nonce = builder.nonce;
-        expiresTime = builder.expiresTime;
-        objectType = builder.objectType;
-        version = builder.payload.getVersion();
-        stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream();
-        payload = builder.payload;
-    }
-
-    @Override
-    public Command getCommand() {
-        return Command.OBJECT;
-    }
-
-    public byte[] getNonce() {
-        return nonce;
-    }
-
-    public void setNonce(byte[] nonce) {
-        this.nonce = nonce;
-    }
-
-    public long getExpiresTime() {
-        return expiresTime;
-    }
-
-    public long getType() {
-        return objectType;
-    }
-
-    public ObjectPayload getPayload() {
-        return payload;
-    }
-
-    public long getVersion() {
-        return version;
-    }
-
-    public long getStream() {
-        return stream;
-    }
-
-    public InventoryVector getInventoryVector() {
-        return InventoryVector.fromHash(
-                Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
-        );
-    }
-
-    private boolean isEncrypted() {
-        return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted();
-    }
-
-    public boolean isSigned() {
-        return payload.isSigned();
-    }
-
-    private byte[] getBytesToSign() {
-        try {
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            writeHeaderWithoutNonce(out);
-            payload.writeBytesToSign(out);
-            return out.toByteArray();
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public void sign(PrivateKey key) {
-        if (payload.isSigned()) {
-            payload.setSignature(cryptography().getSignature(getBytesToSign(), key));
-        }
-    }
-
-    public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException {
-        if (payload instanceof Encrypted) {
-            ((Encrypted) payload).decrypt(key.getPrivateEncryptionKey());
-        }
-    }
-
-    public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException {
-        if (payload instanceof Encrypted) {
-            ((Encrypted) payload).decrypt(privateEncryptionKey);
-        }
-    }
-
-    public void encrypt(byte[] publicEncryptionKey) throws IOException {
-        if (payload instanceof Encrypted) {
-            ((Encrypted) payload).encrypt(publicEncryptionKey);
-        }
-    }
-
-    public void encrypt(Pubkey publicKey) {
-        try {
-            if (payload instanceof Encrypted) {
-                ((Encrypted) payload).encrypt(publicKey.getEncryptionKey());
-            }
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public boolean isSignatureValid(Pubkey pubkey) throws IOException {
-        if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
-        return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
-    }
-
-    @Override
-    public void write(OutputStream out) throws IOException {
-        if (nonce == null) {
-            out.write(new byte[8]);
-        } else {
-            out.write(nonce);
-        }
-        out.write(getPayloadBytesWithoutNonce());
-    }
-
-    @Override
-    public void write(ByteBuffer buffer) {
-        if (nonce == null) {
-            buffer.put(new byte[8]);
-        } else {
-            buffer.put(nonce);
-        }
-        buffer.put(getPayloadBytesWithoutNonce());
-    }
-
-    private void writeHeaderWithoutNonce(OutputStream out) throws IOException {
-        Encode.int64(expiresTime, out);
-        Encode.int32(objectType, out);
-        Encode.varInt(version, out);
-        Encode.varInt(stream, out);
-    }
-
-    public byte[] getPayloadBytesWithoutNonce() {
-        try {
-            if (payloadBytes == null) {
-                ByteArrayOutputStream out = new ByteArrayOutputStream();
-                writeHeaderWithoutNonce(out);
-                payload.write(out);
-                payloadBytes = out.toByteArray();
-            }
-            return payloadBytes;
-        } catch (IOException e) {
-            throw new ApplicationException(e);
-        }
-    }
-
-    public static final class Builder {
-        private byte[] nonce;
-        private long expiresTime;
-        private long objectType = -1;
-        private long streamNumber;
-        private ObjectPayload payload;
-
-        public Builder nonce(byte[] nonce) {
-            this.nonce = nonce;
-            return this;
-        }
-
-        public Builder expiresTime(long expiresTime) {
-            this.expiresTime = expiresTime;
-            return this;
-        }
-
-        public Builder objectType(long objectType) {
-            this.objectType = objectType;
-            return this;
-        }
-
-        public Builder objectType(ObjectType objectType) {
-            this.objectType = objectType.getNumber();
-            return this;
-        }
-
-        public Builder stream(long streamNumber) {
-            this.streamNumber = streamNumber;
-            return this;
-        }
-
-        public Builder payload(ObjectPayload payload) {
-            this.payload = payload;
-            if (this.objectType == -1)
-                this.objectType = payload.getType().getNumber();
-            return this;
-        }
-
-        public ObjectMessage build() {
-            return new ObjectMessage(this);
-        }
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-
-        ObjectMessage that = (ObjectMessage) o;
-
-        return expiresTime == that.expiresTime &&
-                objectType == that.objectType &&
-                version == that.version &&
-                stream == that.stream &&
-                Objects.equals(payload, that.payload);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = Arrays.hashCode(nonce);
-        result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32));
-        result = 31 * result + (int) (objectType ^ (objectType >>> 32));
-        result = 31 * result + (int) (version ^ (version >>> 32));
-        result = 31 * result + (int) (stream ^ (stream >>> 32));
-        result = 31 * result + (payload != null ? payload.hashCode() : 0);
-        return result;
-    }
-}
diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
deleted file mode 100644
index 141edfc..0000000
--- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java
+++ /dev/null
@@ -1,733 +0,0 @@
-/*
- * Copyright 2015 Christian Basler
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package ch.dissem.bitmessage.entity;
-
-import ch.dissem.bitmessage.entity.payload.Msg;
-import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
-import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
-import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
-import ch.dissem.bitmessage.entity.valueobject.Label;
-import ch.dissem.bitmessage.entity.valueobject.extended.Attachment;
-import ch.dissem.bitmessage.entity.valueobject.extended.Message;
-import ch.dissem.bitmessage.exception.ApplicationException;
-import ch.dissem.bitmessage.factory.ExtendedEncodingFactory;
-import ch.dissem.bitmessage.factory.Factory;
-import ch.dissem.bitmessage.utils.*;
-
-import java.io.*;
-import java.nio.ByteBuffer;
-import java.util.*;
-import java.util.Collections;
-
-import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED;
-import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE;
-import static ch.dissem.bitmessage.utils.Singleton.cryptography;
-
-/**
- * The unencrypted message to be sent by 'msg' or 'broadcast'.
- */
-public class Plaintext implements Streamable {
-    private static final long serialVersionUID = -5325729856394951079L;
-
-    private final Type type;
-    private final BitmessageAddress from;
-    private final long encoding;
-    private final byte[] message;
-    private final byte[] ackData;
-    private final UUID conversationId;
-    private ExtendedEncoding extendedData;
-    private ObjectMessage ackMessage;
-    private Object id;
-    private InventoryVector inventoryVector;
-    private BitmessageAddress to;
-    private byte[] signature;
-    private Status status;
-    private Long sent;
-    private Long received;
-
-    private Set