Migrated core and extension modules to Kotlin

(Except BitmessageContext and Bytes)
This commit is contained in:
Christian Basler 2017-06-06 16:36:07 +02:00
parent 811625c051
commit fa0e53289c
213 changed files with 9846 additions and 11067 deletions

View File

@ -1,9 +1,19 @@
buildscript {
ext.kotlin_version = '1.1.2-2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
plugins {
id 'com.github.ben-manes.versions' version '0.14.0'
}
subprojects {
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'jacoco'
@ -17,6 +27,9 @@ subprojects {
mavenCentral()
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}
test {
testLogging {

View File

@ -28,6 +28,6 @@ dependencies {
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.4.0'
testCompile project(':cryptography-bc')
}

View File

@ -66,7 +66,22 @@ public class BitmessageContext {
private final boolean sendPubkeyOnIdentityCreation;
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
ctx = new InternalContext(
builder.cryptography,
builder.inventory,
builder.nodeRegistry,
builder.networkHandler,
builder.addressRepo,
builder.messageRepo,
builder.proofOfWorkRepository,
builder.proofOfWorkEngine,
builder.customCommandHandler,
builder.listener,
builder.labeler,
builder.port,
builder.connectionTTL,
builder.connectionLimit
);
labeler = builder.labeler;
ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
@ -103,7 +118,7 @@ public class BitmessageContext {
}
public BitmessageAddress joinChan(String passphrase, String address) {
BitmessageAddress chan = BitmessageAddress.chan(address, passphrase);
BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase);
chan.setAlias(passphrase);
ctx.getAddressRepository().save(chan);
return chan;
@ -111,14 +126,14 @@ public class BitmessageContext {
public BitmessageAddress createChan(String passphrase) {
// FIXME: hardcoded stream number
BitmessageAddress chan = BitmessageAddress.chan(1, passphrase);
BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase);
ctx.getAddressRepository().save(chan);
return chan;
}
public List<BitmessageAddress> createDeterministicAddresses(
String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
List<BitmessageAddress> result = BitmessageAddress.deterministic(
List<BitmessageAddress> result = BitmessageAddress.Companion.deterministic(
passphrase, numberOfAddresses, version, stream, shorter);
for (int i = 0; i < result.size(); i++) {
BitmessageAddress address = result.get(i);
@ -149,7 +164,7 @@ public class BitmessageContext {
}
public void send(final Plaintext msg) {
if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) {
if (msg.getFrom().getPrivateKey() == null) {
throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key.");
}
labeler().markAsSending(msg);
@ -268,7 +283,7 @@ public class BitmessageContext {
}
private void tryToFindBroadcastsForAddress(BitmessageAddress address) {
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) {
for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) {
try {
Broadcast broadcast = (Broadcast) object.getPayload();
broadcast.decrypt(address);

View File

@ -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<Plaintext> 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());
}
}
}
}

View File

@ -0,0 +1,168 @@
/*
* Copyright 2017 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.Plaintext.Status.PUBKEY_REQUESTED
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.LoggerFactory
import java.util.*
internal open class DefaultMessageListener(
private val labeler: Labeler,
private val listener: BitmessageContext.Listener
) : NetworkHandler.MessageListener {
private var ctx by InternalContext
override fun receive(`object`: ObjectMessage) {
val payload = `object`.payload
when (payload.type) {
ObjectType.GET_PUBKEY -> {
receive(`object`, payload as GetPubkey)
}
ObjectType.PUBKEY -> {
receive(`object`, payload as Pubkey)
}
ObjectType.MSG -> {
receive(`object`, payload as Msg)
}
ObjectType.BROADCAST -> {
receive(`object`, payload as Broadcast)
}
null -> {
if (payload is GenericPayload) {
receive(payload)
}
}
else -> {
throw IllegalArgumentException("Unknown payload type " + payload.type!!)
}
}
}
protected fun receive(`object`: ObjectMessage, getPubkey: GetPubkey) {
val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag)
if (identity != null && identity.privateKey != 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`.stream)
}
}
protected fun receive(`object`: ObjectMessage, pubkey: Pubkey) {
val address: BitmessageAddress?
try {
if (pubkey is V4Pubkey) {
address = ctx.addressRepository.findContact(pubkey.tag)
if (address != null) {
pubkey.decrypt(address.publicDecryptionKey)
}
} else {
address = ctx.addressRepository.findContact(pubkey.ripe)
}
if (address != null && address.pubkey == null) {
updatePubkey(address, pubkey)
}
} catch (_: DecryptionFailedException) {}
}
private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) {
address.pubkey = pubkey
LOG.info("Got pubkey for contact " + address)
ctx.addressRepository.save(address)
val messages = ctx.messageRepository.findMessages(PUBKEY_REQUESTED, address)
LOG.info("Sending " + messages.size + " messages for contact " + address)
for (msg in messages) {
ctx.labeler.markAsSending(msg)
ctx.messageRepository.save(msg)
ctx.send(msg)
}
}
protected fun receive(`object`: ObjectMessage, msg: Msg) {
for (identity in ctx.addressRepository.getIdentities()) {
try {
msg.decrypt(identity.privateKey!!.privateEncryptionKey)
val plaintext = msg.plaintext!!
plaintext.to = identity
if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) {
LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
} else {
receive(`object`.inventoryVector, plaintext)
}
break
} catch (_: DecryptionFailedException) {}
}
}
protected fun receive(ack: GenericPayload) {
if (ack.data.size == Msg.ACK_LENGTH) {
ctx.messageRepository.getMessageForAck(ack.data)?.let {
ctx.labeler.markAsAcknowledged(it)
ctx.messageRepository.save(it)
}
}
}
protected fun receive(`object`: ObjectMessage, broadcast: Broadcast) {
val tag = if (broadcast is V5Broadcast) broadcast.tag else null
for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) {
if (tag != null && !Arrays.equals(tag, subscription.tag)) {
continue
}
try {
broadcast.decrypt(subscription.publicDecryptionKey)
if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) {
LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.")
} else {
receive(`object`.inventoryVector, broadcast.plaintext!!)
}
} catch (_: DecryptionFailedException) {}
}
}
protected fun receive(iv: InventoryVector, msg: Plaintext) {
val contact = ctx.addressRepository.getAddress(msg.from.address)
if (contact != null && contact.pubkey == null) {
updatePubkey(contact, msg.from.pubkey!!)
}
msg.inventoryVector = iv
labeler.setLabels(msg)
ctx.messageRepository.save(msg)
listener.receive(msg)
if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) {
msg.ackMessage?.let {
ctx.inventory.storeObject(it)
ctx.networkHandler.offer(it.inventoryVector)
}
}
}
companion object {
private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java)
}
}

View File

@ -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.
* <p>
* On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply.
* </p>
*/
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<Long> 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<Plaintext> 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);
}
}

View File

@ -0,0 +1,235 @@
/*
* Copyright 2017 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.Encrypted
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.payload.*
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.LoggerFactory
import java.util.*
import java.util.concurrent.Executors
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
/**
* 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.
*
*/
class InternalContext(
val cryptography: Cryptography,
val inventory: ch.dissem.bitmessage.ports.Inventory,
val nodeRegistry: NodeRegistry,
val networkHandler: NetworkHandler,
val addressRepository: AddressRepository,
val messageRepository: ch.dissem.bitmessage.ports.MessageRepository,
val proofOfWorkRepository: ProofOfWorkRepository,
val proofOfWorkEngine: ProofOfWorkEngine,
val customCommandHandler: CustomCommandHandler,
listener: BitmessageContext.Listener,
val labeler: Labeler,
val port: Int,
val connectionTTL: Long,
val connectionLimit: Int
) {
private val threadPool = Executors.newCachedThreadPool()
val proofOfWorkService: ProofOfWorkService = ProofOfWorkService()
val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener)
val clientNonce: Long = cryptography.randomNonce()
private val _streams = TreeSet<Long>()
val streams: LongArray
get() = _streams.toLongArray()
init {
instance = this
Singleton.initialize(cryptography)
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
addressRepository.getIdentities().mapTo(_streams) { it.stream }
addressRepository.getSubscriptions().mapTo(_streams) { it.stream }
if (_streams.isEmpty()) {
_streams.add(1L)
}
init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository,
proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler,
networkListener)
}
private fun init(vararg objects: Any) {
objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) }
}
fun send(plaintext: Plaintext) {
if (plaintext.ackMessage != null) {
val expires = UnixTime.now + plaintext.ttl
LOG.info("Expires at " + expires)
proofOfWorkService.doProofOfWorkWithAck(plaintext, expires)
} else {
send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl)
}
}
fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload,
timeToLive: Long) {
val recipient = to ?: from
val expires = UnixTime.now + timeToLive
LOG.info("Expires at " + expires)
val `object` = ObjectMessage(
stream = recipient.stream,
expiresTime = expires,
payload = payload
)
if (`object`.isSigned) {
`object`.sign(
from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity")
)
}
if (payload is Broadcast) {
payload.encrypt()
} else if (payload is Encrypted) {
`object`.encrypt(
recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available")
)
}
proofOfWorkService.doProofOfWork(to, `object`)
}
fun sendPubkey(identity: BitmessageAddress, targetStream: Long) {
val expires = UnixTime.now + TTL.pubkey
LOG.info("Expires at " + expires)
val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity")
val response = ObjectMessage(
expiresTime = expires,
stream = targetStream,
payload = payload
)
response.sign(
identity.privateKey ?: throw IllegalArgumentException("The given address is no identity")
)
response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey))
// TODO: remember that the pubkey is just about to be sent, and on which stream!
proofOfWorkService.doProofOfWork(response)
}
/**
* 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.
*/
fun requestPubkey(contact: BitmessageAddress) {
threadPool.execute {
val stored = addressRepository.getAddress(contact.address)
tryToFindMatchingPubkey(contact)
if (contact.pubkey != null) {
if (stored != null) {
stored.pubkey = contact.pubkey
addressRepository.save(stored)
} else {
addressRepository.save(contact)
}
return@execute
}
if (stored == null) {
addressRepository.save(contact)
}
val expires = UnixTime.now + TTL.getpubkey
LOG.info("Expires at " + expires)
val payload = GetPubkey(contact)
val request = ObjectMessage(
stream = contact.stream,
expiresTime = expires,
payload = payload
)
proofOfWorkService.doProofOfWork(request)
}
}
private fun tryToFindMatchingPubkey(address: BitmessageAddress) {
addressRepository.getAddress(address.address)?.let {
address.alias = it.alias
address.isSubscribed = it.isSubscribed
}
for (`object` in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) {
try {
val pubkey = `object`.payload as Pubkey
if (address.version == 4L) {
val v4Pubkey = pubkey as V4Pubkey
if (Arrays.equals(address.tag, v4Pubkey.tag)) {
v4Pubkey.decrypt(address.publicDecryptionKey)
if (`object`.isSignatureValid(v4Pubkey)) {
address.pubkey = v4Pubkey
addressRepository.save(address)
break
} else {
LOG.info("Found pubkey for $address but signature is invalid")
}
}
} else {
if (Arrays.equals(pubkey.ripe, address.ripe)) {
address.pubkey = pubkey
addressRepository.save(address)
break
}
}
} catch (e: Exception) {
LOG.debug(e.message, e)
}
}
}
fun resendUnacknowledged() {
val messages = messageRepository.findMessagesToResend()
for (message in messages) {
send(message)
messageRepository.save(message)
}
}
interface ContextHolder {
fun setContext(context: InternalContext)
}
companion object {
private val LOG = LoggerFactory.getLogger(InternalContext::class.java)
@JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000
@JvmField val NETWORK_EXTRA_BYTES: Long = 1000
private var instance: InternalContext by Delegates.notNull<InternalContext>()
operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) {
instance = value
}
}
}

View File

@ -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<byte[]> 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();
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2017 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.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.*
import ch.dissem.bitmessage.entity.payload.Msg
import ch.dissem.bitmessage.ports.ProofOfWorkEngine
import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item
import org.slf4j.LoggerFactory
import java.io.IOException
import java.util.*
/**
* @author Christian Basler
*/
class ProofOfWorkService : ProofOfWorkEngine.Callback {
private val ctx by InternalContext
private val cryptography by lazy { ctx.cryptography }
private val powRepo by lazy { ctx.proofOfWorkRepository }
private val messageRepo by lazy { ctx.messageRepository }
fun doMissingProofOfWork(delayInMilliseconds: Long) {
val items = powRepo.getItems()
if (items.isEmpty()) return
// Wait for 30 seconds, to let the application start up before putting heavy load on the CPU
Timer().schedule(object : TimerTask() {
override fun run() {
LOG.info("Doing POW for " + items.size + " tasks.")
for (initialHash in items) {
val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash)
cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes,
this@ProofOfWorkService)
}
}
}, delayInMilliseconds)
}
fun doProofOfWork(`object`: ObjectMessage) {
doProofOfWork(null, `object`)
}
fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) {
val pubkey = recipient?.pubkey
val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE
val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES
powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes)
if (`object`.payload is PlaintextHolder) {
`object`.payload.plaintext?.let {
it.initialHash = cryptography.getInitialHash(`object`)
messageRepo.save(it)
}
}
cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this)
}
fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) {
val ack = plaintext.ackMessage
messageRepo.save(plaintext)
val item = 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 fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) {
val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash)
if (message == null) {
`object`.nonce = nonce
messageRepo.getMessage(initialHash)?.let {
it.inventoryVector = `object`.inventoryVector
it.updateNextTry()
ctx.labeler.markAsSent(it)
messageRepo.save(it)
}
try {
ctx.networkListener.receive(`object`)
} catch (e: IOException) {
LOG.debug(e.message, e)
}
ctx.inventory.storeObject(`object`)
ctx.networkHandler.offer(`object`.inventoryVector)
} else {
message.ackMessage!!.nonce = nonce
val `object` = ObjectMessage.Builder()
.stream(message.stream)
.expiresTime(expirationTime!!)
.payload(Msg(message))
.build()
if (`object`.isSigned) {
`object`.sign(message.from.privateKey!!)
}
if (`object`.payload is Encrypted) {
`object`.encrypt(message.to!!.pubkey!!)
}
doProofOfWork(message.to, `object`)
}
powRepo.removeObject(initialHash)
}
companion object {
private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java)
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2017 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.constants
/**
* Created by chrigu on 03.06.17.
*/
object Network {
@JvmField val NETWORK_MAGIC_NUMBER = 8
@JvmField val HEADER_SIZE = 24
@JvmField val MAX_PAYLOAD_SIZE = 1600003
@JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE
}

View File

@ -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<NetworkAddress> addresses;
private Addr(Builder builder) {
addresses = builder.addresses;
}
@Override
public Command getCommand() {
return Command.ADDR;
}
public List<NetworkAddress> 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<NetworkAddress> addresses = new ArrayList<>();
public Builder addresses(Collection<NetworkAddress> 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);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright 2017 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.OutputStream
import java.nio.ByteBuffer
/**
* The 'addr' command holds a list of known active Bitmessage nodes.
*/
data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.ADDR
override fun write(out: OutputStream) {
Encode.varInt(addresses.size.toLong(), out)
for (address in addresses) {
address.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(addresses.size.toLong(), buffer)
for (address in addresses) {
address.write(buffer)
}
}
}

View File

@ -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<BitmessageAddress> deterministic(String passphrase, int numberOfAddresses,
long version, long stream, boolean shorter) {
List<BitmessageAddress> result = new ArrayList<>(numberOfAddresses);
List<PrivateKey> 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());
}
}

View File

@ -0,0 +1,195 @@
/*
* Copyright 2017 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.utils.AccessCounter
import ch.dissem.bitmessage.utils.Base58
import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.Decode.bytes
import ch.dissem.bitmessage.utils.Decode.varInt
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.Serializable
import java.util.*
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys.
*/
class BitmessageAddress : Serializable {
val version: Long
val stream: Long
val ripe: ByteArray
val tag: ByteArray?
/**
* The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create
* it regardless of address version.
*/
val publicDecryptionKey: ByteArray
val address: String
var privateKey: PrivateKey? = null
private set
var pubkey: Pubkey? = null
set(pubkey) {
if (pubkey != null) {
if (pubkey is V4Pubkey) {
if (!Arrays.equals(tag, pubkey.tag))
throw IllegalArgumentException("Pubkey has incompatible tag")
}
if (!Arrays.equals(ripe, pubkey.ripe))
throw IllegalArgumentException("Pubkey has incompatible ripe")
field = pubkey
}
}
var alias: String? = null
var isSubscribed: Boolean = false
var isChan: Boolean = false
internal constructor(version: Long, stream: Long, ripe: ByteArray) {
this.version = version
this.stream = stream
this.ripe = ripe
val os = ByteArrayOutputStream()
Encode.varInt(version, os)
Encode.varInt(stream, os)
if (version < 4) {
val 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
val 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
val offset = Bytes.numberOfLeadingZeros(ripe)
os.write(ripe, offset, ripe.size - offset)
val checksum = cryptography().doubleSha512(os.toByteArray())
os.write(checksum, 0, 4)
this.address = "BM-" + Base58.encode(os.toByteArray())
}
constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) {
this.pubkey = publicKey
}
constructor(address: String, passphrase: String) : this(address) {
val key = PrivateKey(this, passphrase)
if (!Arrays.equals(ripe, key.pubkey.ripe)) {
throw IllegalArgumentException("Wrong address or passphrase")
}
this.privateKey = key
this.pubkey = key.pubkey
}
constructor(privateKey: PrivateKey) : this(privateKey.pubkey) {
this.privateKey = privateKey
}
constructor(address: String) {
this.address = address
val bytes = Base58.decode(address.substring(3))
val `in` = ByteArrayInputStream(bytes)
val counter = AccessCounter()
this.version = varInt(`in`, counter)
this.stream = varInt(`in`, counter)
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
// test checksum
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
val expectedChecksum = bytes(`in`, 4)
for (i in 0..3) {
if (expectedChecksum[i] != checksum[i])
throw 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)
}
}
override fun toString(): String {
return alias ?: address
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BitmessageAddress) return false
return version == other.version &&
stream == other.stream &&
Arrays.equals(ripe, other.ripe)
}
override fun hashCode(): Int {
return Arrays.hashCode(ripe)
}
fun has(feature: Feature?): Boolean {
return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false
}
companion object {
@JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress {
val result = BitmessageAddress(address, passphrase)
result.isChan = true
return result
}
@JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress {
val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase)
val result = BitmessageAddress(privateKey)
result.isChan = true
return result
}
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int,
version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
val result = ArrayList<BitmessageAddress>(numberOfAddresses)
val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter)
for (pk in privateKeys) {
result.add(BitmessageAddress(pk))
}
return result
}
@JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray {
val out = ByteArrayOutputStream()
Encode.varInt(version, out)
Encode.varInt(stream, out)
out.write(ripe)
return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64)
}
}
}

View File

@ -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);
}
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright 2017 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.Decode.bytes
import ch.dissem.bitmessage.utils.Decode.varString
import ch.dissem.bitmessage.utils.Encode
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* @author Christian Basler
*/
open class CustomMessage(val customCommand: String, private val data: ByteArray? = null) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM
val isError: Boolean
fun getData(): ByteArray {
if (data != null) {
return data
} else {
val out = ByteArrayOutputStream()
write(out)
return out.toByteArray()
}
}
override fun write(out: OutputStream) {
if (data != null) {
Encode.varString(customCommand, out)
out.write(data)
} else {
throw ApplicationException("Tried to write custom message without data. "
+ "Programmer: did you forget to override #write()?")
}
}
override fun write(buffer: ByteBuffer) {
if (data != null) {
Encode.varString(customCommand, buffer)
buffer.put(data)
} else {
throw ApplicationException("Tried to write custom message without data. "
+ "Programmer: did you forget to override #write()?")
}
}
companion object {
val COMMAND_ERROR = "ERROR"
fun read(`in`: InputStream, length: Int): CustomMessage {
val counter = AccessCounter()
return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length()))
}
fun error(message: String): CustomMessage {
return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8")))
}
}
init {
this.isError = COMMAND_ERROR == customCommand
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,19 +14,18 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
package ch.dissem.bitmessage.entity
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
import ch.dissem.bitmessage.exception.DecryptionFailedException
/**
* Used for objects that have encrypted content
*/
public interface Encrypted {
void encrypt(byte[] publicKey) throws IOException;
interface Encrypted {
fun encrypt(publicKey: ByteArray)
void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException;
@Throws(DecryptionFailedException::class)
fun decrypt(privateKey: ByteArray)
boolean isDecrypted();
val isDecrypted: Boolean
}

View File

@ -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<InventoryVector> inventory;
private GetData(Builder builder) {
inventory = builder.inventory;
}
@Override
public Command getCommand() {
return Command.GETDATA;
}
public List<InventoryVector> 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<InventoryVector> inventory = new LinkedList<>();
public Builder addInventoryVector(InventoryVector inventoryVector) {
this.inventory.add(inventoryVector);
return this;
}
public Builder inventory(List<InventoryVector> inventory) {
this.inventory = inventory;
return this;
}
public GetData build() {
return new GetData(this);
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2017 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.OutputStream
import java.nio.ByteBuffer
/**
* The 'getdata' command is used to request objects from a node.
*/
class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.GETDATA
override fun write(out: OutputStream) {
Encode.varInt(inventory.size.toLong(), out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size.toLong(), buffer)
for (iv in inventory) {
iv.write(buffer)
}
}
companion object {
@JvmField val MAX_INVENTORY_SIZE = 50000
}
}

View File

@ -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<InventoryVector> inventory;
private Inv(Builder builder) {
inventory = builder.inventory;
}
public List<InventoryVector> 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<InventoryVector> inventory = new LinkedList<>();
public Builder addInventoryVector(InventoryVector inventoryVector) {
this.inventory.add(inventoryVector);
return this;
}
public Builder inventory(List<InventoryVector> inventory) {
this.inventory = inventory;
return this;
}
public Inv build() {
return new Inv(this);
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2017 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.OutputStream
import java.nio.ByteBuffer
/**
* The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items.
*/
class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.INV
override fun write(out: OutputStream) {
Encode.varInt(inventory.size.toLong(), out)
for (iv in inventory) {
iv.write(out)
}
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(inventory.size.toLong(), buffer)
for (iv in inventory) {
iv.write(buffer)
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,15 +14,15 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
package ch.dissem.bitmessage.entity
/**
* A command can hold a network message payload
*/
public interface MessagePayload extends Streamable {
Command getCommand();
interface MessagePayload : Streamable {
val command: Command
enum Command {
enum class Command {
VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2017 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.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.IOException
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* A network message is exchanged between two nodes.
*/
data class NetworkMessage(
/**
* The actual data, a message or an object. Not to be confused with objectPayload.
*/
val payload: MessagePayload
) : Streamable {
/**
* First 4 bytes of sha512(payload)
*/
private fun getChecksum(bytes: ByteArray): ByteArray {
val d = cryptography().sha512(bytes)
return byteArrayOf(d[0], d[1], d[2], d[3])
}
@Throws(IOException::class)
override fun write(out: OutputStream) {
// magic
Encode.int32(MAGIC.toLong(), out)
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = payload.command.name.toLowerCase()
out.write(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.write(0x0)
}
val 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.size.toLong(), out)
// checksum
out.write(getChecksum(payloadBytes))
// 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.
*/
fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer {
return ByteBuffer.wrap(writeHeader(headerBuffer))
}
/**
* For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer]
* 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 fun write(buffer: ByteBuffer) {
val payloadBytes = writeHeader(buffer)
buffer.put(payloadBytes)
}
private fun writeHeader(out: ByteBuffer): ByteArray {
// magic
Encode.int32(MAGIC.toLong(), out)
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
val command = payload.command.name.toLowerCase()
out.put(command.toByteArray(charset("ASCII")))
for (i in command.length..11) {
out.put(0.toByte())
}
val 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.size.toLong(), out)
// checksum
out.put(getChecksum(payloadBytes))
// message payload
return payloadBytes
}
companion object {
/**
* Magic value indicating message origin network, and used to seek to next message when stream state is unknown
*/
val MAGIC = 0xE9BEB4D9.toInt()
val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array()
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,228 @@
/*
* Copyright 2017 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 ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
/**
* The 'object' command sends an object that is shared throughout the network.
*/
data class ObjectMessage(
var nonce: ByteArray? = null,
val expiresTime: Long,
val payload: ObjectPayload,
val type: Long,
/**
* The object's version
*/
val version: Long,
val stream: Long
) : MessagePayload {
override val command: MessagePayload.Command = MessagePayload.Command.OBJECT
constructor(
nonce: ByteArray? = null,
expiresTime: Long,
payload: ObjectPayload,
stream: Long
) : this(
nonce,
expiresTime,
payload,
payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"),
payload.version,
stream
)
val inventoryVector: InventoryVector
get() {
return InventoryVector(Bytes.truncate(cryptography().doubleSha512(
nonce ?: throw IllegalStateException("nonce must be set"),
payloadBytesWithoutNonce
), 32))
}
private val isEncrypted: Boolean
get() = payload is Encrypted && !payload.isDecrypted
val isSigned: Boolean
get() = payload.isSigned
private val bytesToSign: ByteArray
get() {
try {
val out = ByteArrayOutputStream()
writeHeaderWithoutNonce(out)
payload.writeBytesToSign(out)
return out.toByteArray()
} catch (e: IOException) {
throw ApplicationException(e)
}
}
fun sign(key: PrivateKey) {
if (payload.isSigned) {
payload.signature = cryptography().getSignature(bytesToSign, key)
}
}
@Throws(DecryptionFailedException::class)
fun decrypt(key: PrivateKey) {
if (payload is Encrypted) {
payload.decrypt(key.privateEncryptionKey)
}
}
@Throws(DecryptionFailedException::class)
fun decrypt(privateEncryptionKey: ByteArray) {
if (payload is Encrypted) {
payload.decrypt(privateEncryptionKey)
}
}
fun encrypt(publicEncryptionKey: ByteArray) {
if (payload is Encrypted) {
payload.encrypt(publicEncryptionKey)
}
}
fun encrypt(publicKey: Pubkey) {
try {
if (payload is Encrypted) {
payload.encrypt(publicKey.encryptionKey)
}
} catch (e: IOException) {
throw ApplicationException(e)
}
}
fun isSignatureValid(pubkey: Pubkey): Boolean {
if (isEncrypted) throw IllegalStateException("Payload must be decrypted first")
return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey)
}
override fun write(out: OutputStream) {
out.write(nonce ?: ByteArray(8))
out.write(payloadBytesWithoutNonce)
}
override fun write(buffer: ByteBuffer) {
buffer.put(nonce ?: ByteArray(8))
buffer.put(payloadBytesWithoutNonce)
}
private fun writeHeaderWithoutNonce(out: OutputStream) {
Encode.int64(expiresTime, out)
Encode.int32(type, out)
Encode.varInt(version, out)
Encode.varInt(stream, out)
}
val payloadBytesWithoutNonce: ByteArray by lazy {
val out = ByteArrayOutputStream()
writeHeaderWithoutNonce(out)
payload.write(out)
out.toByteArray()
}
class Builder {
private var nonce: ByteArray? = null
private var expiresTime: Long = 0
private var objectType: Long? = null
private var streamNumber: Long = 0
private var payload: ObjectPayload? = null
fun nonce(nonce: ByteArray): Builder {
this.nonce = nonce
return this
}
fun expiresTime(expiresTime: Long): Builder {
this.expiresTime = expiresTime
return this
}
fun objectType(objectType: Long): Builder {
this.objectType = objectType
return this
}
fun objectType(objectType: ObjectType): Builder {
this.objectType = objectType.number
return this
}
fun stream(streamNumber: Long): Builder {
this.streamNumber = streamNumber
return this
}
fun payload(payload: ObjectPayload): Builder {
this.payload = payload
if (this.objectType == null)
this.objectType = payload.type?.number
return this
}
fun build(): ObjectMessage {
return ObjectMessage(
nonce = nonce,
expiresTime = expiresTime,
type = objectType!!,
version = payload!!.version,
stream = if (streamNumber > 0) streamNumber else payload!!.stream,
payload = payload!!
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ObjectMessage) return false
return expiresTime == other.expiresTime &&
type == other.type &&
version == other.version &&
stream == other.stream &&
payload == other.payload
}
override fun hashCode(): Int {
var result = Arrays.hashCode(nonce)
result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt()
result = 31 * result + (type xor type.ushr(32)).toInt()
result = 31 * result + (version xor version.ushr(32)).toInt()
result = 31 * result + (stream xor stream.ushr(32)).toInt()
result = 31 * result + (payload.hashCode())
return result
}
}

View File

@ -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<Label> labels;
private byte[] initialHash;
private long ttl;
private int retries;
private Long nextTry;
private Plaintext(Builder builder) {
id = builder.id;
inventoryVector = builder.inventoryVector;
type = builder.type;
from = builder.from;
to = builder.to;
encoding = builder.encoding;
message = builder.message;
ackData = builder.ackData;
if (builder.ackMessage != null && builder.ackMessage.length > 0) {
ackMessage = Factory.getObjectMessage(
3,
new ByteArrayInputStream(builder.ackMessage),
builder.ackMessage.length);
}
signature = builder.signature;
status = builder.status;
sent = builder.sent;
received = builder.received;
labels = builder.labels;
ttl = builder.ttl;
retries = builder.retries;
nextTry = builder.nextTry;
conversationId = builder.conversation;
}
public static Plaintext read(Type type, InputStream in) throws IOException {
return readWithoutSignature(type, in)
.signature(Decode.varBytes(in))
.received(UnixTime.now())
.build();
}
public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException {
long version = Decode.varInt(in);
return new Builder(type)
.addressVersion(version)
.stream(Decode.varInt(in))
.behaviorBitfield(Decode.int32(in))
.publicSigningKey(Decode.bytes(in, 64))
.publicEncryptionKey(Decode.bytes(in, 64))
.nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0)
.extraBytes(version >= 3 ? Decode.varInt(in) : 0)
.destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null)
.encoding(Decode.varInt(in))
.message(Decode.varBytes(in))
.ackMessage(type == Type.MSG ? Decode.varBytes(in) : null);
}
public InventoryVector getInventoryVector() {
return inventoryVector;
}
public void setInventoryVector(InventoryVector inventoryVector) {
this.inventoryVector = inventoryVector;
}
public Type getType() {
return type;
}
public byte[] getMessage() {
return message;
}
public BitmessageAddress getFrom() {
return from;
}
public BitmessageAddress getTo() {
return to;
}
public void setTo(BitmessageAddress to) {
if (this.to.getVersion() != 0)
throw new IllegalStateException("Correct address already set");
if (!Arrays.equals(this.to.getRipe(), to.getRipe())) {
throw new IllegalArgumentException("RIPEs don't match");
}
this.to = to;
}
public Set<Label> getLabels() {
return labels;
}
public Encoding getEncoding() {
return Encoding.fromCode(encoding);
}
public long getStream() {
return from.getStream();
}
public byte[] getSignature() {
return signature;
}
public void setSignature(byte[] signature) {
this.signature = signature;
}
public boolean isUnread() {
for (Label label : labels) {
if (label.getType() == Label.Type.UNREAD) {
return true;
}
}
return false;
}
public void write(OutputStream out, boolean includeSignature) throws IOException {
Encode.varInt(from.getVersion(), out);
Encode.varInt(from.getStream(), out);
if (from.getPubkey() == null) {
Encode.int32(0, out);
byte[] empty = new byte[64];
out.write(empty);
out.write(empty);
if (from.getVersion() >= 3) {
Encode.varInt(0, out);
Encode.varInt(0, out);
}
} else {
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
out.write(from.getPubkey().getSigningKey(), 1, 64);
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
if (from.getVersion() >= 3) {
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
Encode.varInt(from.getPubkey().getExtraBytes(), out);
}
}
if (type == Type.MSG) {
out.write(to.getRipe());
}
Encode.varInt(encoding, out);
Encode.varInt(message.length, out);
out.write(message);
if (type == Type.MSG) {
if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
ByteArrayOutputStream ack = new ByteArrayOutputStream();
getAckMessage().write(ack);
Encode.varBytes(ack.toByteArray(), out);
} else {
Encode.varInt(0, out);
}
}
if (includeSignature) {
if (signature == null) {
Encode.varInt(0, out);
} else {
Encode.varInt(signature.length, out);
out.write(signature);
}
}
}
public void write(ByteBuffer buffer, boolean includeSignature) {
Encode.varInt(from.getVersion(), buffer);
Encode.varInt(from.getStream(), buffer);
if (from.getPubkey() == null) {
Encode.int32(0, buffer);
byte[] empty = new byte[64];
buffer.put(empty);
buffer.put(empty);
if (from.getVersion() >= 3) {
Encode.varInt(0, buffer);
Encode.varInt(0, buffer);
}
} else {
Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer);
buffer.put(from.getPubkey().getSigningKey(), 1, 64);
buffer.put(from.getPubkey().getEncryptionKey(), 1, 64);
if (from.getVersion() >= 3) {
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer);
Encode.varInt(from.getPubkey().getExtraBytes(), buffer);
}
}
if (type == Type.MSG) {
buffer.put(to.getRipe());
}
Encode.varInt(encoding, buffer);
Encode.varInt(message.length, buffer);
buffer.put(message);
if (type == Type.MSG) {
if (to.has(Feature.DOES_ACK) && getAckMessage() != null) {
Encode.varBytes(Encode.bytes(getAckMessage()), buffer);
} else {
Encode.varInt(0, buffer);
}
}
if (includeSignature) {
if (signature == null) {
Encode.varInt(0, buffer);
} else {
Encode.varInt(signature.length, buffer);
buffer.put(signature);
}
}
}
@Override
public void write(OutputStream out) throws IOException {
write(out, true);
}
@Override
public void write(ByteBuffer buffer) {
write(buffer, true);
}
public Object getId() {
return id;
}
public void setId(long id) {
if (this.id != null) throw new IllegalStateException("ID already set");
this.id = id;
}
public Long getSent() {
return sent;
}
public Long getReceived() {
return received;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) {
sent = UnixTime.now();
}
this.status = status;
}
public long getTTL() {
return ttl;
}
public int getRetries() {
return retries;
}
public Long getNextTry() {
return nextTry;
}
public void updateNextTry() {
if (to != null) {
if (nextTry == null) {
if (sent != null && to.has(Feature.DOES_ACK)) {
nextTry = UnixTime.now(+ttl);
retries++;
}
} else {
nextTry = nextTry + (1 << retries) * ttl;
retries++;
}
}
}
public String getSubject() {
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
String firstLine = s.nextLine();
if (encoding == EXTENDED.code) {
if (Message.TYPE.equals(getExtendedData().getType())) {
return ((Message) extendedData.getContent()).getSubject();
} else {
return null;
}
} else if (encoding == SIMPLE.code) {
return firstLine.substring("Subject:".length()).trim();
} else if (firstLine.length() > 50) {
return firstLine.substring(0, 50).trim() + "...";
} else {
return firstLine;
}
}
public String getText() {
if (encoding == EXTENDED.code) {
if (Message.TYPE.equals(getExtendedData().getType())) {
return ((Message) extendedData.getContent()).getBody();
} else {
return null;
}
} else {
try {
String text = new String(message, "UTF-8");
if (encoding == SIMPLE.code) {
return text.substring(text.indexOf("\nBody:") + 6);
}
return text;
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
}
}
protected ExtendedEncoding getExtendedData() {
if (extendedData == null && encoding == EXTENDED.code) {
// TODO: make sure errors are properly handled
extendedData = ExtendedEncodingFactory.getInstance().unzip(message);
}
return extendedData;
}
@SuppressWarnings("unchecked")
public <T extends ExtendedEncoding.ExtendedType> T getExtendedData(Class<T> type) {
ExtendedEncoding extendedData = getExtendedData();
if (extendedData == null) {
return null;
}
if (type == null || type.isInstance(extendedData.getContent())) {
return (T) extendedData.getContent();
}
return null;
}
public List<InventoryVector> getParents() {
if (getExtendedData() != null && Message.TYPE.equals(getExtendedData().getType())) {
return ((Message) extendedData.getContent()).getParents();
} else {
return Collections.emptyList();
}
}
public List<Attachment> getFiles() {
if (Message.TYPE.equals(getExtendedData().getType())) {
return ((Message) extendedData.getContent()).getFiles();
} else {
return Collections.emptyList();
}
}
public UUID getConversationId() {
return conversationId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Plaintext plaintext = (Plaintext) o;
return Objects.equals(encoding, plaintext.encoding) &&
Objects.equals(from, plaintext.from) &&
Arrays.equals(message, plaintext.message) &&
Objects.equals(getAckMessage(), plaintext.getAckMessage()) &&
Arrays.equals(to == null ? null : to.getRipe(), plaintext.to == null ? null : plaintext.to.getRipe()) &&
Arrays.equals(signature, plaintext.signature) &&
Objects.equals(status, plaintext.status) &&
Objects.equals(sent, plaintext.sent) &&
Objects.equals(received, plaintext.received) &&
Objects.equals(labels, plaintext.labels);
}
@Override
public int hashCode() {
return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels);
}
public void addLabels(Label... labels) {
if (labels != null) {
Collections.addAll(this.labels, labels);
}
}
public void addLabels(Collection<Label> labels) {
if (labels != null) {
this.labels.addAll(labels);
}
}
public void removeLabel(Label.Type type) {
Iterator<Label> iterator = labels.iterator();
while (iterator.hasNext()) {
Label label = iterator.next();
if (label.getType() == type) {
iterator.remove();
}
}
}
public byte[] getAckData() {
return ackData;
}
public ObjectMessage getAckMessage() {
if (ackMessage == null) {
ackMessage = Factory.createAck(this);
}
return ackMessage;
}
public void setInitialHash(byte[] initialHash) {
this.initialHash = initialHash;
}
public byte[] getInitialHash() {
return initialHash;
}
@Override
public String toString() {
String subject = getSubject();
if (subject == null || subject.length() == 0) {
return Strings.hex(initialHash).toString();
} else {
return subject;
}
}
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
long code;
Encoding(long code) {
this.code = code;
}
public long getCode() {
return code;
}
public static Encoding fromCode(long code) {
for (Encoding e : values()) {
if (e.getCode() == code) {
return e;
}
}
return null;
}
}
public enum Status {
DRAFT,
// For sent messages
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
SENT_ACKNOWLEDGED,
RECEIVED
}
public enum Type {
MSG, BROADCAST
}
public static final class Builder {
private Object id;
private InventoryVector inventoryVector;
private Type type;
private BitmessageAddress from;
private BitmessageAddress to;
private long addressVersion;
private long stream;
private int behaviorBitfield;
private byte[] publicSigningKey;
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private byte[] destinationRipe;
private long encoding;
private byte[] message = new byte[0];
private byte[] ackData;
private byte[] ackMessage;
private byte[] signature;
private Long sent;
private Long received;
private Status status;
private Set<Label> labels = new LinkedHashSet<>();
private long ttl;
private int retries;
private Long nextTry;
private UUID conversation;
public Builder(Type type) {
this.type = type;
}
public Builder id(Object id) {
this.id = id;
return this;
}
public Builder IV(InventoryVector iv) {
this.inventoryVector = iv;
return this;
}
public Builder from(BitmessageAddress address) {
from = address;
return this;
}
public Builder to(BitmessageAddress address) {
if (type != Type.MSG && to != null)
throw new IllegalArgumentException("recipient address only allowed for msg");
to = address;
return this;
}
private Builder addressVersion(long addressVersion) {
this.addressVersion = addressVersion;
return this;
}
private Builder stream(long stream) {
this.stream = stream;
return this;
}
private Builder behaviorBitfield(int behaviorBitfield) {
this.behaviorBitfield = behaviorBitfield;
return this;
}
private Builder publicSigningKey(byte[] publicSigningKey) {
this.publicSigningKey = publicSigningKey;
return this;
}
private Builder publicEncryptionKey(byte[] publicEncryptionKey) {
this.publicEncryptionKey = publicEncryptionKey;
return this;
}
private Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
this.nonceTrialsPerByte = nonceTrialsPerByte;
return this;
}
private Builder extraBytes(long extraBytes) {
this.extraBytes = extraBytes;
return this;
}
private Builder destinationRipe(byte[] ripe) {
if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg");
this.destinationRipe = ripe;
return this;
}
public Builder encoding(Encoding encoding) {
this.encoding = encoding.getCode();
return this;
}
private Builder encoding(long encoding) {
this.encoding = encoding;
return this;
}
public Builder message(ExtendedEncoding message) {
this.encoding = EXTENDED.getCode();
this.message = message.zip();
return this;
}
public Builder message(String subject, String message) {
try {
this.encoding = SIMPLE.getCode();
this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
return this;
}
public Builder message(byte[] message) {
this.message = message;
return this;
}
public Builder ackMessage(byte[] ack) {
if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg");
this.ackMessage = ack;
return this;
}
public Builder ackData(byte[] ackData) {
if (type != Type.MSG && ackData != null)
throw new IllegalArgumentException("ackMessage only allowed for msg");
this.ackData = ackData;
return this;
}
public Builder signature(byte[] signature) {
this.signature = signature;
return this;
}
public Builder sent(Long sent) {
this.sent = sent;
return this;
}
public Builder received(Long received) {
this.received = received;
return this;
}
public Builder status(Status status) {
this.status = status;
return this;
}
public Builder labels(Collection<Label> labels) {
this.labels.addAll(labels);
return this;
}
public Builder ttl(long ttl) {
this.ttl = ttl;
return this;
}
public Builder retries(int retries) {
this.retries = retries;
return this;
}
public Builder nextTry(Long nextTry) {
this.nextTry = nextTry;
return this;
}
public Builder conversation(UUID id) {
this.conversation = id;
return this;
}
public Plaintext build() {
if (from == null) {
from = new BitmessageAddress(Factory.createPubkey(
addressVersion,
stream,
publicSigningKey,
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
));
}
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
to = new BitmessageAddress(0, 0, destinationRipe);
}
if (type == Type.MSG && ackMessage == null && ackData == null) {
ackData = cryptography().randomBytes(Msg.ACK_LENGTH);
}
if (ttl <= 0) {
ttl = TTL.msg();
}
if (conversation == null) {
conversation = UUID.randomUUID();
}
return new Plaintext(this);
}
}
}

View File

@ -0,0 +1,706 @@
/*
* Copyright 2017 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.Plaintext.Encoding.EXTENDED
import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE
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 ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.*
import java.nio.ByteBuffer
import java.util.*
import java.util.Collections
import kotlin.collections.HashSet
/**
* The unencrypted message to be sent by 'msg' or 'broadcast'.
*/
class Plaintext private constructor(
val type: Type,
val from: BitmessageAddress,
to: BitmessageAddress?,
val encodingCode: Long,
val message: ByteArray,
val ackData: ByteArray?,
ackMessage: Lazy<ObjectMessage?>,
val conversationId: UUID = UUID.randomUUID(),
var inventoryVector: InventoryVector? = null,
var signature: ByteArray? = null,
val received: Long? = null,
var initialHash: ByteArray? = null,
ttl: Long = TTL.msg,
val labels: MutableSet<Label> = HashSet(),
status: Status
) : Streamable {
var id: Any? = null
set(id) {
if (this.id != null) throw IllegalStateException("ID already set")
field = id
}
var to: BitmessageAddress? = to
set(to) {
if (to == null) {
return
}
if (this.to != null) {
if (this.to!!.version != 0L)
throw IllegalStateException("Correct address already set")
if (!Arrays.equals(this.to!!.ripe, to.ripe)) {
throw IllegalArgumentException("RIPEs don't match")
}
}
field = to
}
val stream: Long
get() = to?.stream ?: from.stream
val extendedData: ExtendedEncoding? by lazy {
if (encodingCode == EXTENDED.code) {
ExtendedEncodingFactory.unzip(message)
} else {
null
}
}
val ackMessage: ObjectMessage? by ackMessage
var status: Status = status
set(status) {
if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) {
sent = UnixTime.now
}
field = status
}
val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) }
var sent: Long? = null
private set
var retries: Int = 0
private set
var nextTry: Long? = null
private set
val ttl: Long = ttl
@JvmName("getTTL") get
constructor(
type: Type,
from: BitmessageAddress,
to: BitmessageAddress?,
encoding: Encoding,
message: ByteArray,
ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH),
conversationId: UUID = UUID.randomUUID(),
inventoryVector: InventoryVector? = null,
signature: ByteArray? = null,
received: Long? = null,
initialHash: ByteArray? = null,
ttl: Long = TTL.msg,
labels: MutableSet<Label> = HashSet(),
status: Status
) : this(
type,
from,
to,
encoding.code,
message,
ackData,
lazy { Factory.createAck(from, ackData, ttl) },
conversationId,
inventoryVector,
signature,
received,
initialHash,
ttl,
labels,
status
)
constructor(
type: Type,
from: BitmessageAddress,
to: BitmessageAddress?,
encoding: Long,
message: ByteArray,
ackMessage: ByteArray?,
conversationId: UUID = UUID.randomUUID(),
inventoryVector: InventoryVector? = null,
signature: ByteArray? = null,
received: Long? = null,
initialHash: ByteArray? = null,
ttl: Long = TTL.msg,
labels: MutableSet<Label> = HashSet(),
status: Status
) : this(
type,
from,
to,
encoding,
message,
null,
lazy {
if (ackMessage != null && ackMessage.isNotEmpty()) {
Factory.getObjectMessage(
3,
ByteArrayInputStream(ackMessage),
ackMessage.size)
} else null
},
conversationId,
inventoryVector,
signature,
received,
initialHash,
ttl,
labels,
status
)
constructor(builder: Builder) : this(
builder.type,
builder.from!!,
builder.to,
builder.encoding,
builder.message,
builder.ackData,
lazy {
val ackMsg = builder.ackMessage
if (ackMsg != null && ackMsg.isNotEmpty()) {
Factory.getObjectMessage(
3,
ByteArrayInputStream(ackMsg),
ackMsg.size)
} else {
Factory.createAck(builder.from!!, builder.ackData, builder.ttl)
}
},
builder.conversation ?: UUID.randomUUID(),
builder.inventoryVector,
builder.signature,
builder.received,
null,
builder.ttl,
builder.labels,
builder.status ?: Status.RECEIVED
)
fun write(out: OutputStream, includeSignature: Boolean) {
Encode.varInt(from.version, out)
Encode.varInt(from.stream, out)
if (from.pubkey == null) {
Encode.int32(0, out)
val empty = ByteArray(64)
out.write(empty)
out.write(empty)
if (from.version >= 3) {
Encode.varInt(0, out)
Encode.varInt(0, out)
}
} else {
Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), out)
out.write(from.pubkey!!.signingKey, 1, 64)
out.write(from.pubkey!!.encryptionKey, 1, 64)
if (from.version >= 3) {
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, out)
Encode.varInt(from.pubkey!!.extraBytes, out)
}
}
if (type == Type.MSG) {
out.write(to!!.ripe)
}
Encode.varInt(encodingCode, out)
Encode.varInt(message.size.toLong(), out)
out.write(message)
if (type == Type.MSG) {
if (to?.has(Feature.DOES_ACK) ?: false) {
val ack = ByteArrayOutputStream()
ackMessage?.write(ack)
Encode.varBytes(ack.toByteArray(), out)
} else {
Encode.varInt(0, out)
}
}
if (includeSignature) {
if (signature == null) {
Encode.varInt(0, out)
} else {
Encode.varInt(signature!!.size.toLong(), out)
out.write(signature!!)
}
}
}
fun write(buffer: ByteBuffer, includeSignature: Boolean) {
Encode.varInt(from.version, buffer)
Encode.varInt(from.stream, buffer)
if (from.pubkey == null) {
Encode.int32(0, buffer)
val empty = ByteArray(64)
buffer.put(empty)
buffer.put(empty)
if (from.version >= 3) {
Encode.varInt(0, buffer)
Encode.varInt(0, buffer)
}
} else {
Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), buffer)
buffer.put(from.pubkey!!.signingKey, 1, 64)
buffer.put(from.pubkey!!.encryptionKey, 1, 64)
if (from.version >= 3) {
Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer)
Encode.varInt(from.pubkey!!.extraBytes, buffer)
}
}
if (type == Type.MSG) {
buffer.put(to!!.ripe)
}
Encode.varInt(encodingCode, buffer)
Encode.varInt(message.size.toLong(), buffer)
buffer.put(message)
if (type == Type.MSG) {
if (to!!.has(Feature.DOES_ACK) && ackMessage != null) {
Encode.varBytes(Encode.bytes(ackMessage!!), buffer)
} else {
Encode.varInt(0, buffer)
}
}
if (includeSignature) {
val sig = signature
if (sig == null) {
Encode.varInt(0, buffer)
} else {
Encode.varInt(sig.size.toLong(), buffer)
buffer.put(sig)
}
}
}
override fun write(out: OutputStream) {
write(out, true)
}
override fun write(buffer: ByteBuffer) {
write(buffer, true)
}
fun updateNextTry() {
if (to != null) {
if (nextTry == null) {
if (sent != null && to!!.has(Feature.DOES_ACK)) {
nextTry = UnixTime.now + ttl
retries++
}
} else {
nextTry = nextTry!! + (1 shl retries) * ttl
retries++
}
}
}
val subject: String?
get() {
val s = Scanner(ByteArrayInputStream(message), "UTF-8")
val firstLine = s.nextLine()
if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) {
return (extendedData!!.content as Message?)?.subject
} else {
return null
}
} else if (encodingCode == SIMPLE.code) {
return firstLine.substring("Subject:".length).trim { it <= ' ' }
} else if (firstLine.length > 50) {
return firstLine.substring(0, 50).trim { it <= ' ' } + "..."
} else {
return firstLine
}
}
val text: String?
get() {
if (encodingCode == EXTENDED.code) {
if (Message.TYPE == extendedData?.type) {
return (extendedData?.content as Message?)?.body
} else {
return null
}
} else {
val text = String(message)
if (encodingCode == SIMPLE.code) {
return text.substring(text.indexOf("\nBody:") + 6)
}
return text
}
}
fun <T : ExtendedEncoding.ExtendedType> getExtendedData(type: Class<T>): T? {
val extendedData = extendedData ?: return null
if (type.isInstance(extendedData.content)) {
@Suppress("UNCHECKED_CAST")
return extendedData.content as T
}
return null
}
val parents: List<InventoryVector>
get() {
val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).parents
} else {
return emptyList()
}
}
val files: List<Attachment>
get() {
val extendedData = extendedData ?: return emptyList()
if (Message.TYPE == extendedData.type) {
return (extendedData.content as Message).files
} else {
return emptyList()
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Plaintext) return false
return encoding == other.encoding &&
from == other.from &&
Arrays.equals(message, other.message) &&
ackMessage == other.ackMessage &&
Arrays.equals(to?.ripe, other.to?.ripe) &&
Arrays.equals(signature, other.signature) &&
status == other.status &&
sent == other.sent &&
received == other.received &&
labels == other.labels
}
override fun hashCode(): Int {
return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels)
}
fun addLabels(vararg labels: Label) {
Collections.addAll(this.labels, *labels)
}
fun addLabels(labels: Collection<Label>?) {
if (labels != null) {
this.labels.addAll(labels)
}
}
fun removeLabel(type: Label.Type) {
labels.removeIf { it.type == type }
}
fun isUnread(): Boolean {
return labels.any { it.type == Label.Type.UNREAD }
}
override fun toString(): String {
val subject = subject
if (subject?.isNotEmpty() ?: false) {
return subject!!
} else {
return Strings.hex(
initialHash ?: return super.toString()
)
}
}
enum class Encoding constructor(code: Long) {
IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3);
var code: Long = 0
internal set
init {
this.code = code
}
companion object {
@JvmStatic fun fromCode(code: Long): Encoding? {
for (e in values()) {
if (e.code == code) {
return e
}
}
return null
}
}
}
enum class Status {
DRAFT,
// For sent messages
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
SENT_ACKNOWLEDGED,
RECEIVED
}
enum class Type {
MSG, BROADCAST
}
class Builder(internal val type: Type) {
internal var id: Any? = null
internal var inventoryVector: InventoryVector? = null
internal var from: BitmessageAddress? = null
internal var to: BitmessageAddress? = null
private var addressVersion: Long = 0
private var stream: Long = 0
private var behaviorBitfield: Int = 0
private var publicSigningKey: ByteArray? = null
private var publicEncryptionKey: ByteArray? = null
private var nonceTrialsPerByte: Long = 0
private var extraBytes: Long = 0
private var destinationRipe: ByteArray? = null
internal var encoding: Long = 0
internal var message = ByteArray(0)
internal var ackData: ByteArray? = null
internal var ackMessage: ByteArray? = null
internal var signature: ByteArray? = null
internal var sent: Long? = null
internal var received: Long? = null
internal var status: Status? = null
internal val labels = LinkedHashSet<Label>()
internal var ttl: Long = 0
internal var retries: Int = 0
internal var nextTry: Long? = null
internal var conversation: UUID? = null
fun id(id: Any): Builder {
this.id = id
return this
}
fun IV(iv: InventoryVector?): Builder {
this.inventoryVector = iv
return this
}
fun from(address: BitmessageAddress): Builder {
from = address
return this
}
fun to(address: BitmessageAddress): Builder {
if (type != Type.MSG && to != null)
throw IllegalArgumentException("recipient address only allowed for msg")
to = address
return this
}
fun addressVersion(addressVersion: Long): Builder {
this.addressVersion = addressVersion
return this
}
fun stream(stream: Long): Builder {
this.stream = stream
return this
}
fun behaviorBitfield(behaviorBitfield: Int): Builder {
this.behaviorBitfield = behaviorBitfield
return this
}
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
this.publicSigningKey = publicSigningKey
return this
}
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
this.publicEncryptionKey = publicEncryptionKey
return this
}
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
this.nonceTrialsPerByte = nonceTrialsPerByte
return this
}
fun extraBytes(extraBytes: Long): Builder {
this.extraBytes = extraBytes
return this
}
fun destinationRipe(ripe: ByteArray?): Builder {
if (type != Type.MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg")
this.destinationRipe = ripe
return this
}
fun encoding(encoding: Encoding): Builder {
this.encoding = encoding.code
return this
}
fun encoding(encoding: Long): Builder {
this.encoding = encoding
return this
}
fun message(message: ExtendedEncoding): Builder {
this.encoding = EXTENDED.code
this.message = message.zip()
return this
}
fun message(subject: String, message: String): Builder {
try {
this.encoding = SIMPLE.code
this.message = "Subject:$subject\nBody:$message".toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
throw ApplicationException(e)
}
return this
}
fun message(message: ByteArray): Builder {
this.message = message
return this
}
fun ackMessage(ack: ByteArray?): Builder {
if (type != Type.MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg")
this.ackMessage = ack
return this
}
fun ackData(ackData: ByteArray?): Builder {
if (type != Type.MSG && ackData != null)
throw IllegalArgumentException("ackMessage only allowed for msg")
this.ackData = ackData
return this
}
fun signature(signature: ByteArray): Builder {
this.signature = signature
return this
}
fun sent(sent: Long?): Builder {
this.sent = sent
return this
}
fun received(received: Long?): Builder {
this.received = received
return this
}
fun status(status: Status): Builder {
this.status = status
return this
}
fun labels(labels: Collection<Label>): Builder {
this.labels.addAll(labels)
return this
}
fun ttl(ttl: Long): Builder {
this.ttl = ttl
return this
}
fun retries(retries: Int): Builder {
this.retries = retries
return this
}
fun nextTry(nextTry: Long?): Builder {
this.nextTry = nextTry
return this
}
fun conversation(id: UUID): Builder {
this.conversation = id
return this
}
fun build(): Plaintext {
if (from == null) {
from = BitmessageAddress(Factory.createPubkey(
addressVersion,
stream,
publicSigningKey!!,
publicEncryptionKey!!,
nonceTrialsPerByte,
extraBytes,
behaviorBitfield
))
}
if (to == null && type != Type.BROADCAST && destinationRipe != null) {
to = BitmessageAddress(0, 0, destinationRipe!!)
}
if (type == Type.MSG && ackMessage == null && ackData == null) {
ackData = cryptography().randomBytes(Msg.ACK_LENGTH)
}
if (ttl <= 0) {
ttl = TTL.msg
}
return Plaintext(this)
}
}
companion object {
@JvmStatic fun read(type: Type, `in`: InputStream): Plaintext {
return readWithoutSignature(type, `in`)
.signature(Decode.varBytes(`in`))
.received(UnixTime.now)
.build()
}
@JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder {
val version = Decode.varInt(`in`)
return Builder(type)
.addressVersion(version)
.stream(Decode.varInt(`in`))
.behaviorBitfield(Decode.int32(`in`))
.publicSigningKey(Decode.bytes(`in`, 64))
.publicEncryptionKey(Decode.bytes(`in`, 64))
.nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0)
.extraBytes(if (version >= 3) Decode.varInt(`in`) else 0)
.destinationRipe(if (type == Type.MSG) Decode.bytes(`in`, 20) else null)
.encoding(Decode.varInt(`in`))
.message(Decode.varBytes(`in`))
.ackMessage(if (type == Type.MSG) Decode.varBytes(`in`) else null)
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,8 +14,8 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
package ch.dissem.bitmessage.entity
public interface PlaintextHolder {
Plaintext getPlaintext();
interface PlaintextHolder {
val plaintext: Plaintext?
}

View File

@ -1,31 +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 java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
/**
* An object that can be written to an {@link OutputStream}
*/
public interface Streamable extends Serializable {
void write(OutputStream stream) throws IOException;
void write(ByteBuffer buffer);
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2017 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 java.io.OutputStream
import java.io.Serializable
import java.nio.ByteBuffer
/**
* An object that can be written to an [OutputStream]
*/
interface Streamable : Serializable {
fun write(out: OutputStream)
fun write(buffer: ByteBuffer)
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,30 +14,27 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity;
package ch.dissem.bitmessage.entity
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* The 'verack' command answers a 'version' command, accepting the other node's version.
*/
public class VerAck implements MessagePayload {
private static final long serialVersionUID = -4302074845199181687L;
class VerAck : MessagePayload {
@Override
public Command getCommand() {
return Command.VERACK;
}
override val command: MessagePayload.Command = MessagePayload.Command.VERACK
@Override
public void write(OutputStream stream) throws IOException {
override fun write(out: OutputStream) {
// 'verack' doesn't have any payload, so there is nothing to write
}
@Override
public void write(ByteBuffer buffer) {
override fun write(buffer: ByteBuffer) {
// 'verack' doesn't have any payload, so there is nothing to write
}
companion object {
private val serialVersionUID = -4302074845199181687L
}
}

View File

@ -1,246 +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.BitmessageContext;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* The 'version' command advertises this node's latest supported protocol version upon initiation.
*/
public class Version implements MessagePayload {
private static final long serialVersionUID = 7219240857343176567L;
/**
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
* version is lower but continue with the connection if it is higher.
*/
private final int version;
/**
* bitfield of features to be enabled for this connection
*/
private final long services;
/**
* standard UNIX timestamp in seconds
*/
private final long timestamp;
/**
* The network address of the node receiving this message (not including the time or stream number)
*/
private final NetworkAddress addrRecv;
/**
* The network address of the node emitting this message (not including the time or stream number and the ip itself
* is ignored by the receiver)
*/
private final NetworkAddress addrFrom;
/**
* Random nonce used to detect connections to self.
*/
private final long nonce;
/**
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
*/
private final String userAgent;
/**
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
* stream numbers.
*/
private final long[] streams;
private Version(Builder builder) {
version = builder.version;
services = builder.services;
timestamp = builder.timestamp;
addrRecv = builder.addrRecv;
addrFrom = builder.addrFrom;
nonce = builder.nonce;
userAgent = builder.userAgent;
streams = builder.streamNumbers;
}
public int getVersion() {
return version;
}
public long getServices() {
return services;
}
public boolean provides(Service service) {
return service != null && service.isEnabled(services);
}
public long getTimestamp() {
return timestamp;
}
public NetworkAddress getAddrRecv() {
return addrRecv;
}
public NetworkAddress getAddrFrom() {
return addrFrom;
}
public long getNonce() {
return nonce;
}
public String getUserAgent() {
return userAgent;
}
public long[] getStreams() {
return streams;
}
@Override
public Command getCommand() {
return Command.VERSION;
}
@Override
public void write(OutputStream stream) throws IOException {
Encode.int32(version, stream);
Encode.int64(services, stream);
Encode.int64(timestamp, stream);
addrRecv.write(stream, true);
addrFrom.write(stream, true);
Encode.int64(nonce, stream);
Encode.varString(userAgent, stream);
Encode.varIntList(streams, stream);
}
@Override
public void write(ByteBuffer buffer) {
Encode.int32(version, buffer);
Encode.int64(services, buffer);
Encode.int64(timestamp, buffer);
addrRecv.write(buffer, true);
addrFrom.write(buffer, true);
Encode.int64(nonce, buffer);
Encode.varString(userAgent, buffer);
Encode.varIntList(streams, buffer);
}
public static final class Builder {
private int version;
private long services;
private long timestamp;
private NetworkAddress addrRecv;
private NetworkAddress addrFrom;
private long nonce;
private String userAgent;
private long[] streamNumbers;
public Builder defaults(long clientNonce) {
version = BitmessageContext.CURRENT_VERSION;
services = Service.getServiceFlag(Service.NODE_NETWORK);
timestamp = UnixTime.now();
userAgent = "/Jabit:0.0.1/";
streamNumbers = new long[]{1};
nonce = clientNonce;
return this;
}
public Builder version(int version) {
this.version = version;
return this;
}
public Builder services(Service... services) {
this.services = Service.getServiceFlag(services);
return this;
}
public Builder services(long services) {
this.services = services;
return this;
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder addrRecv(NetworkAddress addrRecv) {
this.addrRecv = addrRecv;
return this;
}
public Builder addrFrom(NetworkAddress addrFrom) {
this.addrFrom = addrFrom;
return this;
}
public Builder nonce(long nonce) {
this.nonce = nonce;
return this;
}
public Builder userAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
public Builder streams(long... streamNumbers) {
this.streamNumbers = streamNumbers;
return this;
}
public Version build() {
return new Version(this);
}
}
public enum Service {
NODE_NETWORK(1);
// TODO: NODE_SSL(2);
long flag;
Service(long flag) {
this.flag = flag;
}
public boolean isEnabled(long flag) {
return (flag & this.flag) != 0;
}
public static long getServiceFlag(Service... services) {
long flag = 0;
for (Service service : services) {
flag |= service.flag;
}
return flag;
}
}
}

View File

@ -0,0 +1,204 @@
/*
* Copyright 2017 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.BitmessageContext
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.UnixTime
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* The 'version' command advertises this node's latest supported protocol version upon initiation.
*/
class Version constructor(
/**
* Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's
* version is lower but continue with the connection if it is higher.
*/
val version: Int = BitmessageContext.CURRENT_VERSION,
/**
* bitfield of features to be enabled for this connection
*/
val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK),
/**
* standard UNIX timestamp in seconds
*/
val timestamp: Long = UnixTime.now,
/**
* The network address of the node receiving this message (not including the time or stream number)
*/
val addrRecv: NetworkAddress,
/**
* The network address of the node emitting this message (not including the time or stream number and the ip itself
* is ignored by the receiver)
*/
val addrFrom: NetworkAddress,
/**
* Random nonce used to detect connections to self.
*/
val nonce: Long,
/**
* User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes.
*/
val userAgent: String = "/Jabit:0.0.1/",
/**
* The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000
* stream numbers.
*/
val streams: LongArray = longArrayOf(1)
) : MessagePayload {
fun provides(service: Service?): Boolean {
return service != null && service.isEnabled(services)
}
override val command: MessagePayload.Command = MessagePayload.Command.VERSION
override fun write(out: OutputStream) {
Encode.int32(version.toLong(), out)
Encode.int64(services, out)
Encode.int64(timestamp, out)
addrRecv.write(out, true)
addrFrom.write(out, true)
Encode.int64(nonce, out)
Encode.varString(userAgent, out)
Encode.varIntList(streams, out)
}
override fun write(buffer: ByteBuffer) {
Encode.int32(version.toLong(), buffer)
Encode.int64(services, buffer)
Encode.int64(timestamp, buffer)
addrRecv.write(buffer, true)
addrFrom.write(buffer, true)
Encode.int64(nonce, buffer)
Encode.varString(userAgent, buffer)
Encode.varIntList(streams, buffer)
}
class Builder {
private var version: Int = 0
private var services: Long = 0
private var timestamp: Long = 0
private var addrRecv: NetworkAddress? = null
private var addrFrom: NetworkAddress? = null
private var nonce: Long = 0
private var userAgent: String? = null
private var streamNumbers: LongArray? = null
fun defaults(clientNonce: Long): Builder {
version = BitmessageContext.CURRENT_VERSION
services = Service.getServiceFlag(Service.NODE_NETWORK)
timestamp = UnixTime.now
userAgent = "/Jabit:0.0.1/"
streamNumbers = longArrayOf(1)
nonce = clientNonce
return this
}
fun version(version: Int): Builder {
this.version = version
return this
}
fun services(vararg services: Service): Builder {
this.services = Service.getServiceFlag(*services)
return this
}
fun services(services: Long): Builder {
this.services = services
return this
}
fun timestamp(timestamp: Long): Builder {
this.timestamp = timestamp
return this
}
fun addrRecv(addrRecv: NetworkAddress): Builder {
this.addrRecv = addrRecv
return this
}
fun addrFrom(addrFrom: NetworkAddress): Builder {
this.addrFrom = addrFrom
return this
}
fun nonce(nonce: Long): Builder {
this.nonce = nonce
return this
}
fun userAgent(userAgent: String): Builder {
this.userAgent = userAgent
return this
}
fun streams(vararg streamNumbers: Long): Builder {
this.streamNumbers = streamNumbers
return this
}
fun build(): Version {
val addrRecv = this.addrRecv
val addrFrom = this.addrFrom
if (addrRecv == null || addrFrom == null) {
throw IllegalStateException("Receiving and sending address must be set")
}
return Version(
version = version,
services = services,
timestamp = timestamp,
addrRecv = addrRecv, addrFrom = addrFrom,
nonce = nonce,
userAgent = userAgent ?: "/Jabit:0.0.1/",
streams = streamNumbers ?: longArrayOf(1)
)
}
}
enum class Service constructor(internal var flag: Long) {
// TODO: NODE_SSL(2);
NODE_NETWORK(1);
fun isEnabled(flag: Long): Boolean {
return (flag and this.flag) != 0L
}
companion object {
fun getServiceFlag(vararg services: Service): Long {
var flag: Long = 0
for (service in services) {
flag = flag or service.flag
}
return flag
}
}
}
}

View File

@ -1,114 +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.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5.
*/
public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder {
private static final long serialVersionUID = 4064521827582239069L;
protected final long stream;
protected CryptoBox encrypted;
protected Plaintext plaintext;
protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
super(version);
this.stream = stream;
this.encrypted = encrypted;
this.plaintext = plaintext;
}
public static long getVersion(BitmessageAddress address) {
return address.getVersion() < 4 ? 4 : 5;
}
@Override
public boolean isSigned() {
return true;
}
@Override
public byte[] getSignature() {
return plaintext.getSignature();
}
@Override
public void setSignature(byte[] signature) {
plaintext.setSignature(signature);
}
@Override
public long getStream() {
return stream;
}
@Override
public Plaintext getPlaintext() {
return plaintext;
}
@Override
public void encrypt(byte[] publicKey) throws IOException {
this.encrypted = new CryptoBox(plaintext, publicKey);
}
public void encrypt() throws IOException {
encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
}
@Override
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey));
}
public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException {
decrypt(address.getPublicDecryptionKey());
}
@Override
public boolean isDecrypted() {
return plaintext != null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Broadcast broadcast = (Broadcast) o;
return stream == broadcast.stream &&
(Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext));
}
@Override
public int hashCode() {
return Objects.hash(stream);
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Encrypted
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST
import ch.dissem.bitmessage.entity.PlaintextHolder
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.util.*
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5.
*/
abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder {
override val isSigned: Boolean = true
override var signature: ByteArray?
get() = plaintext?.signature
set(signature) {
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
}
override fun encrypt(publicKey: ByteArray) {
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
}
fun encrypt() {
encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return))
}
@Throws(DecryptionFailedException::class)
override fun decrypt(privateKey: ByteArray) {
plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return)
}
@Throws(DecryptionFailedException::class)
fun decrypt(address: BitmessageAddress) {
decrypt(address.publicDecryptionKey)
}
override val isDecrypted: Boolean
get() = plaintext != null
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Broadcast) return false
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
}
override fun hashCode(): Int {
return Objects.hash(stream)
}
companion object {
fun getVersion(address: BitmessageAddress): Long {
return if (address.version < 4) 4L else 5L
}
}
}

View File

@ -1,214 +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.payload;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
public class CryptoBox implements Streamable {
private static final long serialVersionUID = 7217659539975573852L;
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
private final byte[] initializationVector;
private final int curveType;
private final byte[] R;
private final byte[] mac;
private byte[] encrypted;
public CryptoBox(Streamable data, byte[] K) throws IOException {
this(Encode.bytes(data), K);
}
public CryptoBox(byte[] data, byte[] K) throws IOException {
curveType = 0x02CA;
// 1. The destination public key is called K.
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
initializationVector = cryptography().randomBytes(16);
// 3. Generate a new random EC key pair with private key called r and public key called R.
byte[] r = cryptography().randomBytes(PRIVATE_KEY_SIZE);
R = cryptography().createPublicKey(r);
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
byte[] P = cryptography().multiply(K, r);
byte[] X = Points.getX(P);
// 5. Use the X component of public key P and calculate the SHA512 hash H.
byte[] H = cryptography().sha512(X);
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
encrypted = cryptography().crypt(true, data, key_e, initializationVector);
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m);
// The resulting data is: IV + R + cipher text + MAC
}
private CryptoBox(Builder builder) {
initializationVector = builder.initializationVector;
curveType = builder.curveType;
R = cryptography().createPoint(builder.xComponent, builder.yComponent);
encrypted = builder.encrypted;
mac = builder.mac;
}
public static CryptoBox read(InputStream stream, int length) throws IOException {
AccessCounter counter = new AccessCounter();
return new Builder()
.IV(Decode.bytes(stream, 16, counter))
.curveType(Decode.uint16(stream, counter))
.X(Decode.shortVarBytes(stream, counter))
.Y(Decode.shortVarBytes(stream, counter))
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
.MAC(Decode.bytes(stream, 32))
.build();
}
/**
* @param k a private key, typically should be 32 bytes long
* @return an InputStream yielding the decrypted data
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
*/
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
// 1. The private key used to decrypt is called k.
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
byte[] P = cryptography().multiply(R, k);
// 3. Use the X component of public key P and calculate the SHA512 hash H.
byte[] H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33));
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
if (!Arrays.equals(mac, calculateMac(key_m))) {
throw new DecryptionFailedException();
}
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
// and the cipher text as payload. The output is the padded input text.
return new ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector));
}
private byte[] calculateMac(byte[] key_m) {
try {
ByteArrayOutputStream macData = new ByteArrayOutputStream();
writeWithoutMAC(macData);
return cryptography().mac(key_m, macData.toByteArray());
} catch (IOException e) {
throw new ApplicationException(e);
}
}
private void writeWithoutMAC(OutputStream out) throws IOException {
out.write(initializationVector);
Encode.int16(curveType, out);
writeCoordinateComponent(out, Points.getX(R));
writeCoordinateComponent(out, Points.getY(R));
out.write(encrypted);
}
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
int offset = Bytes.numberOfLeadingZeros(x);
int length = x.length - offset;
Encode.int16(length, out);
out.write(x, offset, length);
}
private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) {
int offset = Bytes.numberOfLeadingZeros(x);
int length = x.length - offset;
Encode.int16(length, buffer);
buffer.put(x, offset, length);
}
@Override
public void write(OutputStream stream) throws IOException {
writeWithoutMAC(stream);
stream.write(mac);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(initializationVector);
Encode.int16(curveType, buffer);
writeCoordinateComponent(buffer, Points.getX(R));
writeCoordinateComponent(buffer, Points.getY(R));
buffer.put(encrypted);
buffer.put(mac);
}
public static final class Builder {
private byte[] initializationVector;
private int curveType;
private byte[] xComponent;
private byte[] yComponent;
private byte[] encrypted;
private byte[] mac;
public Builder IV(byte[] initializationVector) {
this.initializationVector = initializationVector;
return this;
}
public Builder curveType(int curveType) {
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType);
this.curveType = curveType;
return this;
}
public Builder X(byte[] xComponent) {
this.xComponent = xComponent;
return this;
}
public Builder Y(byte[] yComponent) {
this.yComponent = yComponent;
return this;
}
private Builder encrypted(byte[] encrypted) {
this.encrypted = encrypted;
return this;
}
public Builder MAC(byte[] mac) {
this.mac = mac;
return this;
}
public CryptoBox build() {
return new CryptoBox(this);
}
}
}

View File

@ -0,0 +1,210 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.*
import ch.dissem.bitmessage.utils.Singleton.cryptography
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
class CryptoBox : Streamable {
private val initializationVector: ByteArray
private val curveType: Int
private val R: ByteArray
private val mac: ByteArray
private var encrypted: ByteArray
constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K)
constructor(data: ByteArray, K: ByteArray) {
curveType = 0x02CA
// 1. The destination public key is called K.
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
initializationVector = cryptography().randomBytes(16)
// 3. Generate a new random EC key pair with private key called r and public key called R.
val r = cryptography().randomBytes(PRIVATE_KEY_SIZE)
R = cryptography().createPublicKey(r)
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
val P = cryptography().multiply(K, r)
val X = Points.getX(P)
// 5. Use the X component of public key P and calculate the SHA512 hash H.
val H = cryptography().sha512(X)
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
val key_e = Arrays.copyOfRange(H, 0, 32)
val key_m = Arrays.copyOfRange(H, 32, 64)
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
encrypted = cryptography().crypt(true, data, key_e, initializationVector)
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
mac = calculateMac(key_m)
// The resulting data is: IV + R + cipher text + MAC
}
private constructor(builder: Builder) {
initializationVector = builder.initializationVector!!
curveType = builder.curveType
R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!)
encrypted = builder.encrypted!!
mac = builder.mac!!
}
/**
* @param k a private key, typically should be 32 bytes long
* *
* @return an InputStream yielding the decrypted data
* *
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
* *
* @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption)
*/
@Throws(DecryptionFailedException::class)
fun decrypt(k: ByteArray): InputStream {
// 1. The private key used to decrypt is called k.
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
val P = cryptography().multiply(R, k)
// 3. Use the X component of public key P and calculate the SHA512 hash H.
val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33))
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
val key_e = Arrays.copyOfRange(H, 0, 32)
val key_m = Arrays.copyOfRange(H, 32, 64)
// 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data.
// 6. Compare MAC with MAC'. If not equal, decryption will fail.
if (!Arrays.equals(mac, calculateMac(key_m))) {
throw DecryptionFailedException()
}
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
// and the cipher text as payload. The output is the padded input text.
return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector))
}
private fun calculateMac(key_m: ByteArray): ByteArray {
val macData = ByteArrayOutputStream()
writeWithoutMAC(macData)
return cryptography().mac(key_m, macData.toByteArray())
}
private fun writeWithoutMAC(out: OutputStream) {
out.write(initializationVector)
Encode.int16(curveType.toLong(), out)
writeCoordinateComponent(out, Points.getX(R))
writeCoordinateComponent(out, Points.getY(R))
out.write(encrypted)
}
private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length.toLong(), out)
out.write(x, offset, length)
}
private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) {
val offset = Bytes.numberOfLeadingZeros(x)
val length = x.size - offset
Encode.int16(length.toLong(), buffer)
buffer.put(x, offset, length)
}
override fun write(out: OutputStream) {
writeWithoutMAC(out)
out.write(mac)
}
override fun write(buffer: ByteBuffer) {
buffer.put(initializationVector)
Encode.int16(curveType.toLong(), buffer)
writeCoordinateComponent(buffer, Points.getX(R))
writeCoordinateComponent(buffer, Points.getY(R))
buffer.put(encrypted)
buffer.put(mac)
}
class Builder {
internal var initializationVector: ByteArray? = null
internal var curveType: Int = 0
internal var xComponent: ByteArray? = null
internal var yComponent: ByteArray? = null
internal var encrypted: ByteArray? = null
internal var mac: ByteArray? = null
fun IV(initializationVector: ByteArray): Builder {
this.initializationVector = initializationVector
return this
}
fun curveType(curveType: Int): Builder {
if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType)
this.curveType = curveType
return this
}
fun X(xComponent: ByteArray): Builder {
this.xComponent = xComponent
return this
}
fun Y(yComponent: ByteArray): Builder {
this.yComponent = yComponent
return this
}
fun encrypted(encrypted: ByteArray): Builder {
this.encrypted = encrypted
return this
}
fun MAC(mac: ByteArray): Builder {
this.mac = mac
return this
}
fun build(): CryptoBox {
return CryptoBox(this)
}
}
companion object {
private val LOG = LoggerFactory.getLogger(CryptoBox::class.java)
fun read(stream: InputStream, length: Int): CryptoBox {
val counter = AccessCounter()
return Builder()
.IV(Decode.bytes(stream, 16, counter))
.curveType(Decode.uint16(stream, counter))
.X(Decode.shortVarBytes(stream, counter))
.Y(Decode.shortVarBytes(stream, counter))
.encrypted(Decode.bytes(stream, length - counter.length() - 32))
.MAC(Decode.bytes(stream, 32))
.build()
}
}
}

View File

@ -1,88 +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.payload;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
* have to know what it is.
*/
public class GenericPayload extends ObjectPayload {
private static final long serialVersionUID = -912314085064185940L;
private long stream;
private byte[] data;
public GenericPayload(long version, long stream, byte[] data) {
super(version);
this.stream = stream;
this.data = data;
}
public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException {
return new GenericPayload(version, stream, Decode.bytes(is, length));
}
@Override
public ObjectType getType() {
return null;
}
@Override
public long getStream() {
return stream;
}
public byte[] getData() {
return data;
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(data);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(data);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GenericPayload that = (GenericPayload) o;
if (stream != that.stream) return false;
return Arrays.equals(data, that.data);
}
@Override
public int hashCode() {
int result = (int) (stream ^ (stream >>> 32));
result = 31 * result + Arrays.hashCode(data);
return result;
}
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
/**
* In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really
* have to know what it is.
*/
class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) {
override val type: ObjectType? = null
override fun write(out: OutputStream) {
out.write(data)
}
override fun write(buffer: ByteBuffer) {
buffer.put(data)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GenericPayload) return false
if (stream != other.stream) return false
return Arrays.equals(data, other.data)
}
override fun hashCode(): Int {
var result = (stream xor stream.ushr(32)).toInt()
result = 31 * result + Arrays.hashCode(data)
return result
}
companion object {
fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload {
return GenericPayload(version, stream, Decode.bytes(`is`, length))
}
}
}

View File

@ -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.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Request for a public key.
*/
public class GetPubkey extends ObjectPayload {
private static final long serialVersionUID = -3634516646972610180L;
private long stream;
private byte[] ripeTag;
public GetPubkey(BitmessageAddress address) {
super(address.getVersion());
this.stream = address.getStream();
if (address.getVersion() < 4)
this.ripeTag = address.getRipe();
else
this.ripeTag = address.getTag();
}
private GetPubkey(long version, long stream, byte[] ripeOrTag) {
super(version);
this.stream = stream;
this.ripeTag = ripeOrTag;
}
public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException {
return new GetPubkey(version, stream, Decode.bytes(is, length));
}
/**
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
* address version.
*/
public byte[] getRipeTag() {
return ripeTag;
}
@Override
public ObjectType getType() {
return ObjectType.GET_PUBKEY;
}
@Override
public long getStream() {
return stream;
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(ripeTag);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(ripeTag);
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* Request for a public key.
*/
class GetPubkey : ObjectPayload {
override val type: ObjectType = ObjectType.GET_PUBKEY
override var stream: Long = 0
private set
/**
* @return an array of bytes that represent either the ripe, or the tag of an address, depending on the
* * address version.
*/
val ripeTag: ByteArray
constructor(address: BitmessageAddress) : super(address.version) {
this.stream = address.stream
this.ripeTag = if (address.version < 4) address.ripe else
address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!")
}
private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) {
this.stream = stream
this.ripeTag = ripeOrTag
}
override fun write(out: OutputStream) {
out.write(ripeTag)
}
override fun write(buffer: ByteBuffer) {
buffer.put(ripeTag)
}
companion object {
fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey {
return GetPubkey(version, stream, Decode.bytes(`is`, length))
}
}
}

View File

@ -1,135 +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.payload;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.PlaintextHolder;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Objects;
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
/**
* Used for person-to-person messages.
*/
public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder {
private static final long serialVersionUID = 4327495048296365733L;
public static final int ACK_LENGTH = 32;
private long stream;
private CryptoBox encrypted;
private Plaintext plaintext;
private Msg(long stream, CryptoBox encrypted) {
super(1);
this.stream = stream;
this.encrypted = encrypted;
}
public Msg(Plaintext plaintext) {
super(1);
this.stream = plaintext.getStream();
this.plaintext = plaintext;
}
public static Msg read(InputStream in, long stream, int length) throws IOException {
return new Msg(stream, CryptoBox.read(in, length));
}
@Override
public Plaintext getPlaintext() {
return plaintext;
}
@Override
public ObjectType getType() {
return ObjectType.MSG;
}
@Override
public long getStream() {
return stream;
}
@Override
public boolean isSigned() {
return true;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
plaintext.write(out, false);
}
@Override
public byte[] getSignature() {
return plaintext.getSignature();
}
@Override
public void setSignature(byte[] signature) {
plaintext.setSignature(signature);
}
@Override
public void encrypt(byte[] publicKey) throws IOException {
this.encrypted = new CryptoBox(plaintext, publicKey);
}
@Override
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey));
}
@Override
public boolean isDecrypted() {
return plaintext != null;
}
@Override
public void write(OutputStream out) throws IOException {
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
encrypted.write(out);
}
@Override
public void write(ByteBuffer buffer) {
if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it.");
encrypted.write(buffer);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Msg msg = (Msg) o;
return stream == msg.stream &&
(Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext));
}
@Override
public int hashCode() {
return (int) stream;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.Encrypted
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Type.MSG
import ch.dissem.bitmessage.entity.PlaintextHolder
import ch.dissem.bitmessage.exception.DecryptionFailedException
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* Used for person-to-person messages.
*/
class Msg : ObjectPayload, Encrypted, PlaintextHolder {
override val stream: Long
private var encrypted: CryptoBox?
override var plaintext: Plaintext?
private set
private constructor(stream: Long, encrypted: CryptoBox) : super(1) {
this.stream = stream
this.encrypted = encrypted
this.plaintext = null
}
constructor(plaintext: Plaintext) : super(1) {
this.stream = plaintext.stream
this.encrypted = null
this.plaintext = plaintext
}
override val type: ObjectType = ObjectType.MSG
override val isSigned: Boolean = true
override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
}
override var signature: ByteArray?
get() = plaintext?.signature
set(signature) {
plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available")
}
override fun encrypt(publicKey: ByteArray) {
this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey)
}
@Throws(DecryptionFailedException::class)
override fun decrypt(privateKey: ByteArray) {
plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey))
}
override val isDecrypted: Boolean
get() = plaintext != null
override fun write(out: OutputStream) {
encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
override fun write(buffer: ByteBuffer) {
encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Msg) return false
if (!super.equals(other)) return false
return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext)
}
override fun hashCode(): Int {
return stream.toInt()
}
companion object {
val ACK_LENGTH = 32
fun read(`in`: InputStream, stream: Long, length: Int): Msg {
return Msg(stream, CryptoBox.read(`in`, length))
}
}
}

View File

@ -1,66 +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.payload;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Streamable;
import java.io.IOException;
import java.io.OutputStream;
/**
* The payload of an 'object' command. This is shared by the network.
*/
public abstract class ObjectPayload implements Streamable {
private static final long serialVersionUID = -5034977402902364482L;
private final long version;
protected ObjectPayload(long version) {
this.version = version;
}
public abstract ObjectType getType();
public abstract long getStream();
public long getVersion() {
return version;
}
public boolean isSigned() {
return false;
}
public void writeBytesToSign(OutputStream out) throws IOException {
// nothing to do
}
/**
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
* appended with the data described in this table down to the extra_bytes. Therefore, this must
* be checked and set in the {@link ObjectMessage} object.
*/
public byte[] getSignature() {
return null;
}
public void setSignature(byte[] signature) {
// nothing to do
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.Streamable
import java.io.OutputStream
/**
* The payload of an 'object' command. This is shared by the network.
*/
abstract class ObjectPayload protected constructor(val version: Long) : Streamable {
abstract val type: ObjectType?
abstract val stream: Long
open val isSigned: Boolean = false
open fun writeBytesToSign(out: OutputStream) {
// nothing to do
}
/**
* @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time,
* * appended with the data described in this table down to the extra_bytes. Therefore, this must
* * be checked and set in the [ObjectMessage] object.
*/
open var signature: ByteArray? = null
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,31 +14,20 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.entity.payload;
package ch.dissem.bitmessage.entity.payload
/**
* Known types for 'object' messages. Must not be used where an unknown type must be resent.
*/
public enum ObjectType {
enum class ObjectType constructor(val number: Long) {
GET_PUBKEY(0),
PUBKEY(1),
MSG(2),
BROADCAST(3);
int number;
ObjectType(int number) {
this.number = number;
}
public static ObjectType fromNumber(long number) {
for (ObjectType type : values()) {
if (type.number == number) return type;
}
return null;
}
public long getNumber() {
return number;
companion object {
fun fromNumber(number: Long): ObjectType? {
return values().firstOrNull { it.number == number }
}
}
}

View File

@ -1,121 +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.payload;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
*/
public abstract class Pubkey extends ObjectPayload {
private static final long serialVersionUID = -6634533361454999619L;
public final static long LATEST_VERSION = 4;
protected Pubkey(long version) {
super(version);
}
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey));
}
public abstract byte[] getSigningKey();
public abstract byte[] getEncryptionKey();
public abstract int getBehaviorBitfield();
public byte[] getRipe() {
return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey()));
}
public long getNonceTrialsPerByte() {
return 0;
}
public long getExtraBytes() {
return 0;
}
public void writeUnencrypted(OutputStream out) throws IOException {
write(out);
}
public void writeUnencrypted(ByteBuffer buffer){
write(buffer);
}
protected byte[] add0x04(byte[] key) {
if (key.length == 65) return key;
byte[] result = new byte[65];
result[0] = 4;
System.arraycopy(key, 0, result, 1, 64);
return result;
}
/**
* Bits 0 through 29 are yet undefined
*/
public enum Feature {
/**
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
* messages bound for them.
*/
INCLUDE_DESTINATION(30),
/**
* If true, the receiving node does send acknowledgements (rather than dropping them).
*/
DOES_ACK(31);
private int bit;
Feature(int bitNumber) {
// The Bitmessage Protocol Specification starts counting at the most significant bit,
// thus the slightly awkward calculation.
// https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features
this.bit = 1 << (31 - bitNumber);
}
public static int bitfield(Feature... features) {
int bits = 0;
for (Feature feature : features) {
bits |= feature.bit;
}
return bits;
}
public static Feature[] features(int bitfield) {
ArrayList<Feature> features = new ArrayList<>(Feature.values().length);
for (Feature feature : Feature.values()) {
if ((bitfield & feature.bit) != 0) {
features.add(feature);
}
}
return features.toArray(new Feature[features.size()]);
}
public boolean isActive(int bitfield) {
return (bitfield & bit) != 0;
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
/**
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
*/
abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) {
override val type: ObjectType = ObjectType.PUBKEY
abstract val signingKey: ByteArray
abstract val encryptionKey: ByteArray
abstract val behaviorBitfield: Int
val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) }
open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE
open val extraBytes: Long = NETWORK_EXTRA_BYTES
open fun writeUnencrypted(out: OutputStream) {
write(out)
}
open fun writeUnencrypted(buffer: ByteBuffer) {
write(buffer)
}
/**
* Bits 0 through 29 are yet undefined
*/
enum class Feature constructor(bitNumber: Int) {
/**
* Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg
* messages bound for them.
*/
INCLUDE_DESTINATION(30),
/**
* If true, the receiving node does send acknowledgements (rather than dropping them).
*/
DOES_ACK(31);
// The Bitmessage Protocol Specification starts counting at the most significant bit,
// thus the slightly awkward calculation.
// https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features
private val bit: Int = 1 shl 31 - bitNumber
fun isActive(bitfield: Int): Boolean {
return bitfield and bit != 0
}
companion object {
@JvmStatic fun bitfield(vararg features: Feature): Int {
var bits = 0
for (feature in features) {
bits = bits or feature.bit
}
return bits
}
@JvmStatic fun features(bitfield: Int): Array<Feature> {
val features = ArrayList<Feature>(Feature.values().size)
for (feature in Feature.values()) {
if (bitfield and feature.bit != 0) {
features.add(feature)
}
}
return features.toTypedArray()
}
}
}
companion object {
@JvmField val LATEST_VERSION: Long = 4
fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray {
return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey))
}
fun add0x04(key: ByteArray): ByteArray {
if (key.size == 65) return key
val result = ByteArray(65)
result[0] = 4
System.arraycopy(key, 0, result, 1, 64)
return result
}
}
}

View File

@ -1,133 +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.payload;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* A version 2 public key.
*/
public class V2Pubkey extends Pubkey {
private static final long serialVersionUID = -257598690676510460L;
protected long stream;
protected int behaviorBitfield;
protected byte[] publicSigningKey; // 64 Bytes
protected byte[] publicEncryptionKey; // 64 Bytes
protected V2Pubkey(long version) {
super(version);
}
private V2Pubkey(long version, Builder builder) {
super(version);
stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = add0x04(builder.publicSigningKey);
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
}
public static V2Pubkey read(InputStream is, long stream) throws IOException {
return new V2Pubkey.Builder()
.stream(stream)
.behaviorBitfield((int) Decode.uint32(is))
.publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64))
.build();
}
@Override
public long getVersion() {
return 2;
}
@Override
public ObjectType getType() {
return ObjectType.PUBKEY;
}
@Override
public long getStream() {
return stream;
}
@Override
public byte[] getSigningKey() {
return publicSigningKey;
}
@Override
public byte[] getEncryptionKey() {
return publicEncryptionKey;
}
@Override
public int getBehaviorBitfield() {
return behaviorBitfield;
}
@Override
public void write(OutputStream out) throws IOException {
Encode.int32(behaviorBitfield, out);
out.write(publicSigningKey, 1, 64);
out.write(publicEncryptionKey, 1, 64);
}
@Override
public void write(ByteBuffer buffer) {
Encode.int32(behaviorBitfield, buffer);
buffer.put(publicSigningKey, 1, 64);
buffer.put(publicEncryptionKey, 1, 64);
}
public static class Builder {
private long streamNumber;
private int behaviorBitfield;
private byte[] publicSigningKey;
private byte[] publicEncryptionKey;
public Builder stream(long streamNumber) {
this.streamNumber = streamNumber;
return this;
}
public Builder behaviorBitfield(int behaviorBitfield) {
this.behaviorBitfield = behaviorBitfield;
return this;
}
public Builder publicSigningKey(byte[] publicSigningKey) {
this.publicSigningKey = publicSigningKey;
return this;
}
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
this.publicEncryptionKey = publicEncryptionKey;
return this;
}
public V2Pubkey build() {
return new V2Pubkey(2, this);
}
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* A version 2 public key.
*/
open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) {
override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey
override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey
override fun write(out: OutputStream) {
Encode.int32(behaviorBitfield.toLong(), out)
out.write(signingKey, 1, 64)
out.write(encryptionKey, 1, 64)
}
override fun write(buffer: ByteBuffer) {
Encode.int32(behaviorBitfield.toLong(), buffer)
buffer.put(signingKey, 1, 64)
buffer.put(encryptionKey, 1, 64)
}
class Builder {
internal var streamNumber: Long = 0
internal var behaviorBitfield: Int = 0
internal var publicSigningKey: ByteArray? = null
internal var publicEncryptionKey: ByteArray? = null
fun stream(streamNumber: Long): Builder {
this.streamNumber = streamNumber
return this
}
fun behaviorBitfield(behaviorBitfield: Int): Builder {
this.behaviorBitfield = behaviorBitfield
return this
}
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
this.publicSigningKey = publicSigningKey
return this
}
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
this.publicEncryptionKey = publicEncryptionKey
return this
}
fun build(): V2Pubkey {
return V2Pubkey(
version = 2,
stream = streamNumber,
behaviorBitfield = behaviorBitfield,
signingKey = add0x04(publicSigningKey!!),
encryptionKey = add0x04(publicEncryptionKey!!)
)
}
}
companion object {
fun read(`in`: InputStream, stream: Long): V2Pubkey {
return V2Pubkey(
version = 2,
stream = stream,
behaviorBitfield = Decode.uint32(`in`).toInt(),
signingKey = Decode.bytes(`in`, 64),
encryptionKey = Decode.bytes(`in`, 64)
)
}
}
}

View File

@ -1,175 +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.payload;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Objects;
/**
* A version 3 public key.
*/
public class V3Pubkey extends V2Pubkey {
private static final long serialVersionUID = 6958853116648528319L;
long nonceTrialsPerByte;
long extraBytes;
byte[] signature;
protected V3Pubkey(long version, Builder builder) {
super(version);
stream = builder.streamNumber;
behaviorBitfield = builder.behaviorBitfield;
publicSigningKey = add0x04(builder.publicSigningKey);
publicEncryptionKey = add0x04(builder.publicEncryptionKey);
nonceTrialsPerByte = builder.nonceTrialsPerByte;
extraBytes = builder.extraBytes;
signature = builder.signature;
}
public static V3Pubkey read(InputStream is, long stream) throws IOException {
return new V3Pubkey.Builder()
.stream(stream)
.behaviorBitfield(Decode.int32(is))
.publicSigningKey(Decode.bytes(is, 64))
.publicEncryptionKey(Decode.bytes(is, 64))
.nonceTrialsPerByte(Decode.varInt(is))
.extraBytes(Decode.varInt(is))
.signature(Decode.varBytes(is))
.build();
}
@Override
public void write(OutputStream out) throws IOException {
writeBytesToSign(out);
Encode.varBytes(signature, out);
}
@Override
public void write(ByteBuffer buffer) {
super.write(buffer);
Encode.varInt(nonceTrialsPerByte, buffer);
Encode.varInt(extraBytes, buffer);
Encode.varBytes(signature, buffer);
}
@Override
public long getVersion() {
return 3;
}
public long getNonceTrialsPerByte() {
return nonceTrialsPerByte;
}
public long getExtraBytes() {
return extraBytes;
}
public boolean isSigned() {
return true;
}
public void writeBytesToSign(OutputStream out) throws IOException {
super.write(out);
Encode.varInt(nonceTrialsPerByte, out);
Encode.varInt(extraBytes, out);
}
@Override
public byte[] getSignature() {
return signature;
}
@Override
public void setSignature(byte[] signature) {
this.signature = signature;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V3Pubkey pubkey = (V3Pubkey) o;
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
Objects.equals(extraBytes, pubkey.extraBytes) &&
stream == pubkey.stream &&
behaviorBitfield == pubkey.behaviorBitfield &&
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
}
@Override
public int hashCode() {
return Objects.hash(nonceTrialsPerByte, extraBytes);
}
public static class Builder {
private long streamNumber;
private int behaviorBitfield;
private byte[] publicSigningKey;
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private byte[] signature = new byte[0];
public Builder stream(long streamNumber) {
this.streamNumber = streamNumber;
return this;
}
public Builder behaviorBitfield(int behaviorBitfield) {
this.behaviorBitfield = behaviorBitfield;
return this;
}
public Builder publicSigningKey(byte[] publicSigningKey) {
this.publicSigningKey = publicSigningKey;
return this;
}
public Builder publicEncryptionKey(byte[] publicEncryptionKey) {
this.publicEncryptionKey = publicEncryptionKey;
return this;
}
public Builder nonceTrialsPerByte(long nonceTrialsPerByte) {
this.nonceTrialsPerByte = nonceTrialsPerByte;
return this;
}
public Builder extraBytes(long extraBytes) {
this.extraBytes = extraBytes;
return this;
}
public Builder signature(byte[] signature) {
this.signature = signature;
return this;
}
public V3Pubkey build() {
return new V3Pubkey(3, this);
}
}
}

View File

@ -0,0 +1,150 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
/**
* A version 3 public key.
*/
class V3Pubkey protected constructor(
version: Long, stream: Long, behaviorBitfield: Int,
signingKey: ByteArray, encryptionKey: ByteArray,
override val nonceTrialsPerByte: Long,
override val extraBytes: Long,
override var signature: ByteArray? = null
) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) {
override fun write(out: OutputStream) {
writeBytesToSign(out)
Encode.varBytes(
signature ?: throw IllegalStateException("signature not available"),
out
)
}
override fun write(buffer: ByteBuffer) {
super.write(buffer)
Encode.varInt(nonceTrialsPerByte, buffer)
Encode.varInt(extraBytes, buffer)
Encode.varBytes(
signature ?: throw IllegalStateException("signature not available"),
buffer
)
}
override val isSigned: Boolean = true
override fun writeBytesToSign(out: OutputStream) {
super.write(out)
Encode.varInt(nonceTrialsPerByte, out)
Encode.varInt(extraBytes, out)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is V3Pubkey) return false
return nonceTrialsPerByte == other.nonceTrialsPerByte &&
extraBytes == other.extraBytes &&
stream == other.stream &&
behaviorBitfield == other.behaviorBitfield &&
Arrays.equals(signingKey, other.signingKey) &&
Arrays.equals(encryptionKey, other.encryptionKey)
}
override fun hashCode(): Int {
return Objects.hash(nonceTrialsPerByte, extraBytes)
}
class Builder {
private var streamNumber: Long = 0
private var behaviorBitfield: Int = 0
private var publicSigningKey: ByteArray? = null
private var publicEncryptionKey: ByteArray? = null
private var nonceTrialsPerByte: Long = 0
private var extraBytes: Long = 0
private var signature = ByteArray(0)
fun stream(streamNumber: Long): Builder {
this.streamNumber = streamNumber
return this
}
fun behaviorBitfield(behaviorBitfield: Int): Builder {
this.behaviorBitfield = behaviorBitfield
return this
}
fun publicSigningKey(publicSigningKey: ByteArray): Builder {
this.publicSigningKey = publicSigningKey
return this
}
fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder {
this.publicEncryptionKey = publicEncryptionKey
return this
}
fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder {
this.nonceTrialsPerByte = nonceTrialsPerByte
return this
}
fun extraBytes(extraBytes: Long): Builder {
this.extraBytes = extraBytes
return this
}
fun signature(signature: ByteArray): Builder {
this.signature = signature
return this
}
fun build(): V3Pubkey {
return V3Pubkey(
version = 3,
stream = streamNumber,
behaviorBitfield = behaviorBitfield,
signingKey = publicSigningKey!!,
encryptionKey = publicEncryptionKey!!,
nonceTrialsPerByte = nonceTrialsPerByte,
extraBytes = extraBytes,
signature = signature
)
}
}
companion object {
fun read(`is`: InputStream, stream: Long): V3Pubkey {
return V3Pubkey(
version = 3,
stream = stream,
behaviorBitfield = Decode.int32(`is`),
signingKey = Decode.bytes(`is`, 64),
encryptionKey = Decode.bytes(`is`, 64),
nonceTrialsPerByte = Decode.varInt(`is`),
extraBytes = Decode.varInt(`is`),
signature = Decode.varBytes(`is`)
)
}
}
}

View File

@ -1,67 +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.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5.
*/
public class V4Broadcast extends Broadcast {
private static final long serialVersionUID = 195663108282762711L;
protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) {
super(version, stream, encrypted, plaintext);
}
public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
super(4, senderAddress.getStream(), null, plaintext);
if (senderAddress.getVersion() >= 4)
throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion());
}
public static V4Broadcast read(InputStream in, long stream, int length) throws IOException {
return new V4Broadcast(4, stream, CryptoBox.read(in, length), null);
}
@Override
public ObjectType getType() {
return ObjectType.BROADCAST;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
plaintext.write(out, false);
}
@Override
public void write(OutputStream out) throws IOException {
encrypted.write(out);
}
@Override
public void write(ByteBuffer buffer) {
encrypted.write(buffer);
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
* Broadcasts are version 4 or 5.
*/
open class V4Broadcast : Broadcast {
override val type: ObjectType = ObjectType.BROADCAST
protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext)
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) {
if (senderAddress.version >= 4)
throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version)
}
override fun writeBytesToSign(out: OutputStream) {
plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available")
}
override fun write(out: OutputStream) {
encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted")
}
override fun write(buffer: ByteBuffer) {
encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted")
}
companion object {
fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast {
return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null)
}
}
}

View File

@ -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.entity.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Encrypted;
import ch.dissem.bitmessage.exception.DecryptionFailedException;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
* to create messages to be used in spam or in flooding attacks.
*/
public class V4Pubkey extends Pubkey implements Encrypted {
private static final long serialVersionUID = 1556710353694033093L;
private long stream;
private byte[] tag;
private CryptoBox encrypted;
private V3Pubkey decrypted;
private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) {
super(4);
this.stream = stream;
this.tag = tag;
this.encrypted = encrypted;
}
public V4Pubkey(V3Pubkey decrypted) {
super(4);
this.decrypted = decrypted;
this.stream = decrypted.stream;
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
}
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
if (encrypted)
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
else
return new V4Pubkey(V3Pubkey.read(in, stream));
}
@Override
public void encrypt(byte[] publicKey) throws IOException {
if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption.");
this.encrypted = new CryptoBox(decrypted, publicKey);
}
@Override
public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException {
decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream);
}
@Override
public boolean isDecrypted() {
return decrypted != null;
}
@Override
public void write(OutputStream stream) throws IOException {
stream.write(tag);
encrypted.write(stream);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(tag);
encrypted.write(buffer);
}
@Override
public void writeUnencrypted(OutputStream out) throws IOException {
decrypted.write(out);
}
@Override
public void writeUnencrypted(ByteBuffer buffer) {
decrypted.write(buffer);
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
out.write(tag);
decrypted.writeBytesToSign(out);
}
@Override
public long getVersion() {
return 4;
}
@Override
public ObjectType getType() {
return ObjectType.PUBKEY;
}
@Override
public long getStream() {
return stream;
}
public byte[] getTag() {
return tag;
}
@Override
public byte[] getSigningKey() {
return decrypted.getSigningKey();
}
@Override
public byte[] getEncryptionKey() {
return decrypted.getEncryptionKey();
}
@Override
public int getBehaviorBitfield() {
return decrypted.getBehaviorBitfield();
}
@Override
public byte[] getSignature() {
if (decrypted != null)
return decrypted.getSignature();
else
return null;
}
@Override
public void setSignature(byte[] signature) {
decrypted.setSignature(signature);
}
@Override
public boolean isSigned() {
return true;
}
public long getNonceTrialsPerByte() {
return decrypted.getNonceTrialsPerByte();
}
public long getExtraBytes() {
return decrypted.getExtraBytes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V4Pubkey v4Pubkey = (V4Pubkey) o;
if (stream != v4Pubkey.stream) return false;
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
}
@Override
public int hashCode() {
int result = (int) (stream ^ (stream >>> 32));
result = 31 * result + Arrays.hashCode(tag);
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
return result;
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Encrypted
import ch.dissem.bitmessage.exception.DecryptionFailedException
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
/**
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
* done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and
* use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them
* to create messages to be used in spam or in flooding attacks.
*/
class V4Pubkey : Pubkey, Encrypted {
override val stream: Long
val tag: ByteArray
private var encrypted: CryptoBox? = null
private var decrypted: V3Pubkey? = null
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) {
this.stream = stream
this.tag = tag
this.encrypted = encrypted
}
constructor(decrypted: V3Pubkey) : super(4) {
this.stream = decrypted.stream
this.decrypted = decrypted
this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe)
}
override fun encrypt(publicKey: ByteArray) {
if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.")
this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey)
}
@Throws(DecryptionFailedException::class)
override fun decrypt(privateKey: ByteArray) {
decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream)
}
override val isDecrypted: Boolean
get() = decrypted != null
override fun write(out: OutputStream) {
out.write(tag)
encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun write(buffer: ByteBuffer) {
buffer.put(tag)
encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(out: OutputStream) {
decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeUnencrypted(buffer: ByteBuffer) {
decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted")
}
override fun writeBytesToSign(out: OutputStream) {
out.write(tag)
decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted")
}
override val signingKey: ByteArray
get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted")
override val encryptionKey: ByteArray
get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted")
override val behaviorBitfield: Int
get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted")
override var signature: ByteArray?
get() = decrypted?.signature
set(signature) {
decrypted?.signature = signature
}
override val isSigned: Boolean = true
override val nonceTrialsPerByte: Long
get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted")
override val extraBytes: Long
get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is V4Pubkey) return false
if (stream != other.stream) return false
if (!Arrays.equals(tag, other.tag)) return false
return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null
}
override fun hashCode(): Int {
var result = (stream xor stream.ushr(32)).toInt()
result = 31 * result + Arrays.hashCode(tag)
result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0
return result
}
companion object {
fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey {
if (encrypted)
return V4Pubkey(stream,
Decode.bytes(`in`, 32),
CryptoBox.read(`in`, length - 32))
else
return V4Pubkey(V3Pubkey.read(`in`, stream))
}
}
}

View File

@ -1,66 +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.payload;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
*/
public class V5Broadcast extends V4Broadcast {
private static final long serialVersionUID = 920649721626968644L;
private byte[] tag;
private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) {
super(5, stream, encrypted, null);
this.tag = tag;
}
public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) {
super(5, senderAddress.getStream(), null, plaintext);
if (senderAddress.getVersion() < 4)
throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion());
this.tag = senderAddress.getTag();
}
public static V5Broadcast read(InputStream is, long stream, int length) throws IOException {
return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32));
}
public byte[] getTag() {
return tag;
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
out.write(tag);
super.writeBytesToSign(out);
}
@Override
public void write(OutputStream out) throws IOException {
out.write(tag);
super.write(out);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2017 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.payload
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.utils.Decode
import java.io.InputStream
import java.io.OutputStream
/**
* Users who are subscribed to the sending address will see the message appear in their inbox.
*/
class V5Broadcast : V4Broadcast {
val tag: ByteArray
private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) {
this.tag = tag
}
constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) {
if (senderAddress.version < 4)
throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version)
this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag")
}
override fun writeBytesToSign(out: OutputStream) {
out.write(tag)
super.writeBytesToSign(out)
}
override fun write(out: OutputStream) {
out.write(tag)
super.write(out)
}
companion object {
fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast {
return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32))
}
}
}

View File

@ -1,76 +0,0 @@
package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.msgpack.types.MPMap;
import ch.dissem.msgpack.types.MPString;
import ch.dissem.msgpack.types.MPType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.Objects;
import java.util.zip.DeflaterOutputStream;
/**
* Extended encoding message object.
*/
public class ExtendedEncoding implements Serializable {
private static final long serialVersionUID = 3876871488247305200L;
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncoding.class);
private ExtendedType content;
public ExtendedEncoding(ExtendedType content) {
this.content = content;
}
public String getType() {
if (content == null) {
return null;
} else {
return content.getType();
}
}
public ExtendedType getContent() {
return content;
}
public byte[] zip() {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) {
content.pack().pack(zipper);
}
return out.toByteArray();
} catch (IOException e) {
throw new ApplicationException(e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExtendedEncoding that = (ExtendedEncoding) o;
return Objects.equals(content, that.content);
}
@Override
public int hashCode() {
return Objects.hash(content);
}
public interface Unpacker<T extends ExtendedType> {
String getType();
T unpack(MPMap<MPString, MPType<?>> map);
}
public interface ExtendedType extends Serializable {
String getType();
MPMap<MPString, MPType<?>> pack() throws IOException;
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2017 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.valueobject
import ch.dissem.msgpack.types.MPMap
import ch.dissem.msgpack.types.MPString
import ch.dissem.msgpack.types.MPType
import java.io.ByteArrayOutputStream
import java.io.Serializable
import java.util.zip.DeflaterOutputStream
/**
* Extended encoding message object.
*/
data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable {
val type: String? = content.type
fun zip(): ByteArray {
ByteArrayOutputStream().use { out ->
DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) }
return out.toByteArray()
}
}
interface Unpacker<out T : ExtendedType> {
val type: String
fun unpack(map: MPMap<MPString, MPType<*>>): T
}
interface ExtendedType : Serializable {
val type: String
fun pack(): MPMap<MPString, MPType<*>>
}
}

View File

@ -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.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.utils.Strings;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class InventoryVector implements Streamable, Serializable {
private static final long serialVersionUID = -7349009673063348719L;
/**
* Hash of the object
*/
private final byte[] hash;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InventoryVector)) return false;
InventoryVector that = (InventoryVector) o;
return Arrays.equals(hash, that.hash);
}
@Override
public int hashCode() {
return hash == null ? 0 : Arrays.hashCode(hash);
}
public byte[] getHash() {
return hash;
}
private InventoryVector(byte[] hash) {
if (hash == null) throw new IllegalArgumentException("hash must not be null");
this.hash = hash;
}
public static InventoryVector fromHash(byte[] hash) {
if (hash == null) {
return null;
} else {
return new InventoryVector(hash);
}
}
@Override
public void write(OutputStream out) throws IOException {
out.write(hash);
}
@Override
public void write(ByteBuffer buffer) {
buffer.put(hash);
}
@Override
public String toString() {
return Strings.hex(hash).toString();
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright 2017 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.valueobject
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.utils.Strings
import java.io.OutputStream
import java.nio.ByteBuffer
import java.util.*
data class InventoryVector constructor(
/**
* Hash of the object
*/
val hash: ByteArray) : Streamable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is InventoryVector) return false
return Arrays.equals(hash, other.hash)
}
override fun hashCode(): Int {
return Arrays.hashCode(hash)
}
override fun write(out: OutputStream) {
out.write(hash)
}
override fun write(buffer: ByteBuffer) {
buffer.put(hash)
}
override fun toString(): String {
return Strings.hex(hash).toString()
}
companion object {
@JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? {
return InventoryVector(
hash ?: return null
)
}
}
}

View File

@ -1,89 +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.valueobject;
import java.io.Serializable;
import java.util.Objects;
public class Label implements Serializable {
private static final long serialVersionUID = 831782893630994914L;
private Object id;
private String label;
private Type type;
private int color;
public Label(String label, Type type, int color) {
this.label = label;
this.type = type;
this.color = color;
}
/**
* @return RGBA representation for the color.
*/
public int getColor() {
return color;
}
/**
* @param color RGBA representation for the color.
*/
public void setColor(int color) {
this.color = color;
}
@Override
public String toString() {
return label;
}
public Object getId() {
return id;
}
public void setId(Object id) {
this.id = id;
}
public Type getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Label label1 = (Label) o;
return Objects.equals(label, label1.label);
}
@Override
public int hashCode() {
return Objects.hash(label);
}
public enum Type {
INBOX,
BROADCAST,
DRAFT,
OUTBOX,
SENT,
UNREAD,
TRASH
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2017 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.valueobject
import java.io.Serializable
import java.util.*
data class Label(private val label: String, val type: Label.Type,
/**
* RGBA representation for the color.
*/
var color: Int) : Serializable {
var id: Any? = null
override fun toString(): String {
return label
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Label) return false
return label == other.label
}
override fun hashCode(): Int {
return Objects.hash(label)
}
enum class Type {
INBOX,
BROADCAST,
DRAFT,
OUTBOX,
SENT,
UNREAD,
TRASH
}
}

View File

@ -1,246 +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.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.Version;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.utils.Encode;
import ch.dissem.bitmessage.utils.UnixTime;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* A node's address. It's written in IPv6 format.
*/
public class NetworkAddress implements Streamable {
private static final long serialVersionUID = 2500120578167100300L;
private long time;
/**
* Stream number for this node
*/
private final long stream;
/**
* same service(s) listed in version
*/
private final long services;
/**
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
*/
private final byte[] ipv6;
private final int port;
private NetworkAddress(Builder builder) {
time = builder.time;
stream = builder.stream;
services = builder.services;
ipv6 = builder.ipv6;
port = builder.port;
}
public byte[] getIPv6() {
return ipv6;
}
public int getPort() {
return port;
}
public long getServices() {
return services;
}
public boolean provides(Version.Service service) {
if (service == null) {
return false;
}
return service.isEnabled(services);
}
public long getStream() {
return stream;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public InetAddress toInetAddress() {
try {
return InetAddress.getByAddress(ipv6);
} catch (UnknownHostException e) {
throw new ApplicationException(e);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NetworkAddress that = (NetworkAddress) o;
return port == that.port && Arrays.equals(ipv6, that.ipv6);
}
@Override
public int hashCode() {
int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0;
result = 31 * result + port;
return result;
}
@Override
public String toString() {
return "[" + toInetAddress() + "]:" + port;
}
@Override
public void write(OutputStream stream) throws IOException {
write(stream, false);
}
public void write(OutputStream out, boolean light) throws IOException {
if (!light) {
Encode.int64(time, out);
Encode.int32(stream, out);
}
Encode.int64(services, out);
out.write(ipv6);
Encode.int16(port, out);
}
@Override
public void write(ByteBuffer buffer) {
write(buffer, false);
}
public void write(ByteBuffer buffer, boolean light) {
if (!light) {
Encode.int64(time, buffer);
Encode.int32(stream, buffer);
}
Encode.int64(services, buffer);
buffer.put(ipv6);
Encode.int16(port, buffer);
}
public static final class Builder {
private long time;
private long stream;
private long services = 1;
private byte[] ipv6;
private int port;
public Builder time(final long time) {
this.time = time;
return this;
}
public Builder stream(final long stream) {
this.stream = stream;
return this;
}
public Builder services(final long services) {
this.services = services;
return this;
}
public Builder ip(InetAddress inetAddress) {
byte[] addr = inetAddress.getAddress();
if (addr.length == 16) {
this.ipv6 = addr;
} else if (addr.length == 4) {
this.ipv6 = new byte[16];
this.ipv6[10] = (byte) 0xff;
this.ipv6[11] = (byte) 0xff;
System.arraycopy(addr, 0, this.ipv6, 12, 4);
} else {
throw new IllegalArgumentException("Weird address " + inetAddress);
}
return this;
}
public Builder ipv6(byte[] ipv6) {
this.ipv6 = ipv6;
return this;
}
public Builder ipv6(int p00, int p01, int p02, int p03,
int p04, int p05, int p06, int p07,
int p08, int p09, int p10, int p11,
int p12, int p13, int p14, int p15) {
this.ipv6 = new byte[]{
(byte) p00, (byte) p01, (byte) p02, (byte) p03,
(byte) p04, (byte) p05, (byte) p06, (byte) p07,
(byte) p08, (byte) p09, (byte) p10, (byte) p11,
(byte) p12, (byte) p13, (byte) p14, (byte) p15
};
return this;
}
public Builder ipv4(int p00, int p01, int p02, int p03) {
this.ipv6 = new byte[]{
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
(byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00,
(byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff,
(byte) p00, (byte) p01, (byte) p02, (byte) p03
};
return this;
}
public Builder port(final int port) {
this.port = port;
return this;
}
public Builder address(SocketAddress address) {
if (address instanceof InetSocketAddress) {
InetSocketAddress inetAddress = (InetSocketAddress) address;
ip(inetAddress.getAddress());
port(inetAddress.getPort());
} else {
throw new IllegalArgumentException("Unknown type of address: " + address.getClass());
}
return this;
}
public NetworkAddress build() {
if (time == 0) {
time = UnixTime.now();
}
return new NetworkAddress(this);
}
}
}

View File

@ -0,0 +1,183 @@
/*
* Copyright 2017 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.valueobject
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.Version
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.UnixTime
import java.io.OutputStream
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.nio.ByteBuffer
import java.util.*
/**
* A node's address. It's written in IPv6 format.
*/
data class NetworkAddress constructor(
var time: Long,
/**
* Stream number for this node
*/
val stream: Long,
/**
* same service(s) listed in version
*/
val services: Long,
/**
* IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address
* (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address).
*/
val iPv6: ByteArray,
val port: Int
) : Streamable {
fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false
fun toInetAddress(): InetAddress {
return InetAddress.getByAddress(iPv6)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is NetworkAddress) return false
return port == other.port && Arrays.equals(iPv6, other.iPv6)
}
override fun hashCode(): Int {
var result = Arrays.hashCode(iPv6)
result = 31 * result + port
return result
}
override fun toString(): String {
return "[" + toInetAddress() + "]:" + port
}
override fun write(out: OutputStream) {
write(out, false)
}
fun write(out: OutputStream, light: Boolean) {
if (!light) {
Encode.int64(time, out)
Encode.int32(stream, out)
}
Encode.int64(services, out)
out.write(iPv6)
Encode.int16(port.toLong(), out)
}
override fun write(buffer: ByteBuffer) {
write(buffer, false)
}
fun write(buffer: ByteBuffer, light: Boolean) {
if (!light) {
Encode.int64(time, buffer)
Encode.int32(stream, buffer)
}
Encode.int64(services, buffer)
buffer.put(iPv6)
Encode.int16(port.toLong(), buffer)
}
class Builder {
internal var time: Long? = null
internal var stream: Long = 0
internal var services: Long = 1
internal var ipv6: ByteArray? = null
internal var port: Int = 0
fun time(time: Long): Builder {
this.time = time
return this
}
fun stream(stream: Long): Builder {
this.stream = stream
return this
}
fun services(services: Long): Builder {
this.services = services
return this
}
fun ip(inetAddress: InetAddress): Builder {
val addr = inetAddress.address
if (addr.size == 16) {
this.ipv6 = addr
} else if (addr.size == 4) {
val ipv6 = ByteArray(16)
ipv6[10] = 0xff.toByte()
ipv6[11] = 0xff.toByte()
System.arraycopy(addr, 0, ipv6, 12, 4)
this.ipv6 = ipv6
} else {
throw IllegalArgumentException("Weird address " + inetAddress)
}
return this
}
fun ipv6(ipv6: ByteArray): Builder {
this.ipv6 = ipv6
return this
}
fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int,
p04: Int, p05: Int, p06: Int, p07: Int,
p08: Int, p09: Int, p10: Int, p11: Int,
p12: Int, p13: Int, p14: Int, p15: Int): Builder {
this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte())
return this
}
fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder {
this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte())
return this
}
fun port(port: Int): Builder {
this.port = port
return this
}
fun address(address: SocketAddress): Builder {
if (address is InetSocketAddress) {
val inetAddress = address
ip(inetAddress.address)
port(inetAddress.port)
} else {
throw IllegalArgumentException("Unknown type of address: " + address.javaClass)
}
return this
}
fun build(): NetworkAddress {
return NetworkAddress(
time ?: UnixTime.now, stream, services, ipv6!!, port
)
}
}
}

View File

@ -1,198 +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.valueobject;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
* {@link Pubkey} object.
*/
public class PrivateKey implements Streamable {
private static final long serialVersionUID = 8562555470709110558L;
public static final int PRIVATE_KEY_SIZE = 32;
private final byte[] privateSigningKey;
private final byte[] privateEncryptionKey;
private final Pubkey pubkey;
public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
byte[] privSK;
byte[] pubSK;
byte[] privEK;
byte[] pubEK;
byte[] ripe;
do {
privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE);
privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE);
pubSK = cryptography().createPublicKey(privSK);
pubEK = cryptography().createPublicKey(privEK);
ripe = Pubkey.getRipe(pubSK, pubEK);
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
this.privateSigningKey = privSK;
this.privateEncryptionKey = privEK;
this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, features);
}
public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) {
this.privateSigningKey = privateSigningKey;
this.privateEncryptionKey = privateEncryptionKey;
this.pubkey = pubkey;
}
public PrivateKey(BitmessageAddress address, String passphrase) {
this(address.getVersion(), address.getStream(), passphrase);
}
public PrivateKey(long version, long stream, String passphrase) {
this(new Builder(version, stream, false).seed(passphrase).generate());
}
private PrivateKey(Builder builder) {
this.privateSigningKey = builder.privSK;
this.privateEncryptionKey = builder.privEK;
this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK,
InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES);
}
private static class Builder {
final long version;
final long stream;
final boolean shorter;
byte[] seed;
long nextNonce;
byte[] privSK, privEK;
byte[] pubSK, pubEK;
private Builder(long version, long stream, boolean shorter) {
this.version = version;
this.stream = stream;
this.shorter = shorter;
}
Builder seed(String passphrase) {
try {
seed = passphrase.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
return this;
}
Builder generate() {
long signingKeyNonce = nextNonce;
long encryptionKeyNonce = nextNonce + 1;
byte[] ripe;
do {
privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32);
privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32);
pubSK = cryptography().createPublicKey(privSK);
pubEK = cryptography().createPublicKey(privEK);
ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK));
signingKeyNonce += 2;
encryptionKeyNonce += 2;
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
nextNonce = signingKeyNonce;
return this;
}
}
public static List<PrivateKey> deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) {
List<PrivateKey> result = new ArrayList<>(numberOfAddresses);
Builder builder = new Builder(version, stream, shorter).seed(passphrase);
for (int i = 0; i < numberOfAddresses; i++) {
builder.generate();
result.add(new PrivateKey(builder));
}
return result;
}
public static PrivateKey read(InputStream is) throws IOException {
int version = (int) Decode.varInt(is);
long stream = Decode.varInt(is);
int len = (int) Decode.varInt(is);
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
len = (int) Decode.varInt(is);
byte[] signingKey = Decode.bytes(is, len);
len = (int) Decode.varInt(is);
byte[] encryptionKey = Decode.bytes(is, len);
return new PrivateKey(signingKey, encryptionKey, pubkey);
}
public byte[] getPrivateSigningKey() {
return privateSigningKey;
}
public byte[] getPrivateEncryptionKey() {
return privateEncryptionKey;
}
public Pubkey getPubkey() {
return pubkey;
}
@Override
public void write(OutputStream out) throws IOException {
Encode.varInt(pubkey.getVersion(), out);
Encode.varInt(pubkey.getStream(), out);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pubkey.writeUnencrypted(baos);
Encode.varInt(baos.size(), out);
out.write(baos.toByteArray());
Encode.varInt(privateSigningKey.length, out);
out.write(privateSigningKey);
Encode.varInt(privateEncryptionKey.length, out);
out.write(privateEncryptionKey);
}
@Override
public void write(ByteBuffer buffer) {
Encode.varInt(pubkey.getVersion(), buffer);
Encode.varInt(pubkey.getStream(), buffer);
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pubkey.writeUnencrypted(baos);
Encode.varBytes(baos.toByteArray(), buffer);
} catch (IOException e) {
throw new ApplicationException(e);
}
Encode.varBytes(privateSigningKey, buffer);
Encode.varBytes(privateEncryptionKey, buffer);
}
}

View File

@ -0,0 +1,171 @@
/*
* Copyright 2017 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.valueobject
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.*
import java.nio.ByteBuffer
import java.util.*
/**
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
* [Pubkey] object.
*/
class PrivateKey : Streamable {
val privateSigningKey: ByteArray
val privateEncryptionKey: ByteArray
val pubkey: Pubkey
constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) {
var privSK: ByteArray
var pubSK: ByteArray
var privEK: ByteArray
var pubEK: ByteArray
var ripe: ByteArray
do {
privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE)
privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE)
pubSK = cryptography().createPublicKey(privSK)
pubEK = cryptography().createPublicKey(privEK)
ripe = Pubkey.getRipe(pubSK, pubEK)
} while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
this.privateSigningKey = privSK
this.privateEncryptionKey = privEK
this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, *features)
}
constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) {
this.privateSigningKey = privateSigningKey
this.privateEncryptionKey = privateEncryptionKey
this.pubkey = pubkey
}
constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase)
constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate())
private constructor(builder: Builder) {
this.privateSigningKey = builder.privSK!!
this.privateEncryptionKey = builder.privEK!!
this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES)
}
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
internal var seed: ByteArray? = null
internal var nextNonce: Long = 0
internal var privSK: ByteArray? = null
internal var privEK: ByteArray? = null
internal var pubSK: ByteArray? = null
internal var pubEK: ByteArray? = null
internal fun seed(passphrase: String): Builder {
try {
seed = passphrase.toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
throw ApplicationException(e)
}
return this
}
internal fun generate(): Builder {
var signingKeyNonce = nextNonce
var encryptionKeyNonce = nextNonce + 1
var ripe: ByteArray
do {
privEK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(encryptionKeyNonce)), 32)
privSK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(signingKeyNonce)), 32)
pubSK = cryptography().createPublicKey(privSK!!)
pubEK = cryptography().createPublicKey(privEK!!)
ripe = cryptography().ripemd160(cryptography().sha512(pubSK!!, pubEK!!))
signingKeyNonce += 2
encryptionKeyNonce += 2
} while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
nextNonce = signingKeyNonce
return this
}
}
override fun write(out: OutputStream) {
Encode.varInt(pubkey.version, out)
Encode.varInt(pubkey.stream, out)
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varInt(baos.size().toLong(), out)
out.write(baos.toByteArray())
Encode.varInt(privateSigningKey.size.toLong(), out)
out.write(privateSigningKey)
Encode.varInt(privateEncryptionKey.size.toLong(), out)
out.write(privateEncryptionKey)
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(pubkey.version, buffer)
Encode.varInt(pubkey.stream, buffer)
try {
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varBytes(baos.toByteArray(), buffer)
} catch (e: IOException) {
throw ApplicationException(e)
}
Encode.varBytes(privateSigningKey, buffer)
Encode.varBytes(privateEncryptionKey, buffer)
}
companion object {
@JvmField val PRIVATE_KEY_SIZE = 32
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
val result = ArrayList<PrivateKey>(numberOfAddresses)
val builder = Builder(version, stream, shorter).seed(passphrase)
for (i in 0..numberOfAddresses - 1) {
builder.generate()
result.add(PrivateKey(builder))
}
return result
}
@JvmStatic fun read(`is`: InputStream): PrivateKey {
val version = Decode.varInt(`is`).toInt()
val stream = Decode.varInt(`is`)
val len = Decode.varInt(`is`).toInt()
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
val signingKey = Decode.varBytes(`is`)
val encryptionKey = Decode.varBytes(`is`)
return PrivateKey(signingKey, encryptionKey, pubkey)
}
}
}

View File

@ -1,100 +0,0 @@
package ch.dissem.bitmessage.entity.valueobject.extended;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Objects;
/**
* A "file" attachment as used by extended encoding type messages. Could either be an attachment,
* or used inline to be used by a HTML message, for example.
*/
public class Attachment implements Serializable {
private static final long serialVersionUID = 7319139427666943189L;
private String name;
private byte[] data;
private String type;
private Disposition disposition;
public String getName() {
return name;
}
public byte[] getData() {
return data;
}
public String getType() {
return type;
}
public Disposition getDisposition() {
return disposition;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attachment that = (Attachment) o;
return Objects.equals(name, that.name) &&
Arrays.equals(data, that.data) &&
Objects.equals(type, that.type) &&
disposition == that.disposition;
}
@Override
public int hashCode() {
return Objects.hash(name, data, type, disposition);
}
public enum Disposition {
inline, attachment
}
public static final class Builder {
private String name;
private byte[] data;
private String type;
private Disposition disposition;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder data(byte[] data) {
this.data = data;
return this;
}
public Builder type(String type) {
this.type = type;
return this;
}
public Builder inline() {
this.disposition = Disposition.inline;
return this;
}
public Builder attachment() {
this.disposition = Disposition.attachment;
return this;
}
public Builder disposition(Disposition disposition) {
this.disposition = disposition;
return this;
}
public Attachment build() {
Attachment attachment = new Attachment();
attachment.type = this.type;
attachment.disposition = this.disposition;
attachment.data = this.data;
attachment.name = this.name;
return attachment;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright 2017 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.valueobject.extended
import java.io.Serializable
import java.util.*
/**
* A "file" attachment as used by extended encoding type messages. Could either be an attachment,
* or used inline to be used by a HTML message, for example.
*/
data class Attachment constructor(
val name: String,
val data: ByteArray,
val type: String,
val disposition: Disposition
) : Serializable {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Attachment) return false
return name == other.name &&
Arrays.equals(data, other.data) &&
type == other.type &&
disposition == other.disposition
}
override fun hashCode(): Int {
return Objects.hash(name, data, type, disposition)
}
enum class Disposition {
inline, attachment
}
class Builder {
private var name: String? = null
private var data: ByteArray? = null
private var type: String? = null
private var disposition: Disposition? = null
fun name(name: String): Builder {
this.name = name
return this
}
fun data(data: ByteArray): Builder {
this.data = data
return this
}
fun type(type: String): Builder {
this.type = type
return this
}
fun inline(): Builder {
this.disposition = Disposition.inline
return this
}
fun attachment(): Builder {
this.disposition = Disposition.attachment
return this
}
fun disposition(disposition: Disposition): Builder {
this.disposition = disposition
return this
}
fun build(): Attachment {
return Attachment(name!!, data!!, type!!, disposition!!)
}
}
}

View File

@ -1,219 +0,0 @@
package ch.dissem.bitmessage.entity.valueobject.extended;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.msgpack.types.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.*;
import static ch.dissem.bitmessage.entity.valueobject.extended.Attachment.Disposition.attachment;
import static ch.dissem.bitmessage.utils.Strings.str;
import static ch.dissem.msgpack.types.Utils.mp;
/**
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
* properly with future PyBitmessage implementations.
*/
public class Message implements ExtendedEncoding.ExtendedType {
private static final long serialVersionUID = -2724977231484285467L;
private static final Logger LOG = LoggerFactory.getLogger(Message.class);
public static final String TYPE = "message";
private String subject;
private String body;
private List<InventoryVector> parents;
private List<Attachment> files;
private Message(Builder builder) {
subject = builder.subject;
body = builder.body;
parents = Collections.unmodifiableList(builder.parents);
files = Collections.unmodifiableList(builder.files);
}
@Override
public String getType() {
return TYPE;
}
public String getSubject() {
return subject;
}
public String getBody() {
return body;
}
public List<InventoryVector> getParents() {
return parents;
}
public List<Attachment> getFiles() {
return files;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Message message = (Message) o;
return Objects.equals(subject, message.subject) &&
Objects.equals(body, message.body) &&
Objects.equals(parents, message.parents) &&
Objects.equals(files, message.files);
}
@Override
public int hashCode() {
return Objects.hash(subject, body, parents, files);
}
@Override
public MPMap<MPString, MPType<?>> pack() throws IOException {
MPMap<MPString, MPType<?>> result = new MPMap<>();
result.put(mp(""), mp(TYPE));
result.put(mp("subject"), mp(subject));
result.put(mp("body"), mp(body));
if (!files.isEmpty()) {
MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>();
result.put(mp("files"), items);
for (Attachment file : files) {
MPMap<MPString, MPType<?>> item = new MPMap<>();
item.put(mp("name"), mp(file.getName()));
item.put(mp("data"), mp(file.getData()));
item.put(mp("type"), mp(file.getType()));
item.put(mp("disposition"), mp(file.getDisposition().name()));
items.add(item);
}
}
if (!parents.isEmpty()) {
MPArray<MPBinary> items = new MPArray<>();
result.put(mp("parents"), items);
for (InventoryVector parent : parents) {
items.add(mp(parent.getHash()));
}
}
return result;
}
public static class Builder {
private String subject;
private String body;
private List<InventoryVector> parents = new LinkedList<>();
private List<Attachment> files = new LinkedList<>();
public Builder subject(String subject) {
this.subject = subject;
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder addParent(Plaintext parent) {
if (parent != null) {
InventoryVector iv = parent.getInventoryVector();
if (iv == null) {
LOG.debug("Ignored parent without IV");
} else {
parents.add(iv);
}
}
return this;
}
public Builder addParent(InventoryVector iv) {
if (iv != null) {
parents.add(iv);
}
return this;
}
public Builder addFile(File file, Attachment.Disposition disposition) {
if (file != null) {
try {
files.add(new Attachment.Builder()
.name(file.getName())
.disposition(disposition)
.type(URLConnection.guessContentTypeFromStream(new FileInputStream(file)))
.data(Files.readAllBytes(file.toPath()))
.build());
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
return this;
}
public Builder addFile(Attachment file) {
if (file != null) {
files.add(file);
}
return this;
}
public ExtendedEncoding build() {
return new ExtendedEncoding(new Message(this));
}
}
public static class Unpacker implements ExtendedEncoding.Unpacker<Message> {
@Override
public String getType() {
return TYPE;
}
@Override
public Message unpack(MPMap<MPString, MPType<?>> map) {
Message.Builder builder = new Message.Builder();
builder.subject(str(map.get(mp("subject"))));
builder.body(str(map.get(mp("body"))));
@SuppressWarnings("unchecked")
MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents"));
if (parents != null) {
for (MPBinary parent : parents) {
builder.addParent(InventoryVector.fromHash(parent.getValue()));
}
}
@SuppressWarnings("unchecked")
MPArray<MPMap<MPString, MPType<?>>> files = (MPArray<MPMap<MPString, MPType<?>>>) map.get(mp("files"));
if (files != null) {
for (MPMap<MPString, MPType<?>> item : files) {
Attachment.Builder b = new Attachment.Builder();
b.name(str(item.get(mp("name"))));
b.data(bin(item.get(mp("data"))));
b.type(str(item.get(mp("type"))));
String disposition = str(item.get(mp("disposition")));
if ("inline".equals(disposition)) {
b.inline();
} else if ("attachment".equals(disposition)) {
b.attachment();
}
builder.addFile(b.build());
}
}
return new Message(builder);
}
private byte[] bin(MPType data) {
if (data instanceof MPBinary) {
return ((MPBinary) data).getValue();
} else {
return null;
}
}
}
}

View File

@ -0,0 +1,184 @@
/*
* Copyright 2017 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.valueobject.extended
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.utils.Strings.str
import ch.dissem.msgpack.types.*
import ch.dissem.msgpack.types.Utils.mp
import org.slf4j.LoggerFactory
import java.io.File
import java.io.FileInputStream
import java.io.IOException
import java.net.URLConnection
import java.nio.file.Files
import java.util.*
/**
* Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work
* properly with future PyBitmessage implementations.
*/
data class Message constructor(
val subject: String,
val body: String,
val parents: List<InventoryVector>,
val files: List<Attachment>
) : ExtendedEncoding.ExtendedType {
override val type: String = TYPE
override fun pack(): MPMap<MPString, MPType<*>> {
val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE))
result.put(mp("subject"), mp(subject))
result.put(mp("body"), mp(body))
if (!files.isEmpty()) {
val items = MPArray<MPMap<MPString, MPType<*>>>()
result.put(mp("files"), items)
for (file in files) {
val item = MPMap<MPString, MPType<*>>()
item.put(mp("name"), mp(file.name))
item.put(mp("data"), mp(*file.data))
item.put(mp("type"), mp(file.type))
item.put(mp("disposition"), mp(file.disposition.name))
items.add(item)
}
}
if (!parents.isEmpty()) {
val items = MPArray<MPBinary>()
result.put(mp("parents"), items)
for ((hash) in parents) {
items.add(mp(*hash))
}
}
return result
}
class Builder {
private var subject: String? = null
private var body: String? = null
private val parents = LinkedList<InventoryVector>()
private val files = LinkedList<Attachment>()
fun subject(subject: String): Builder {
this.subject = subject
return this
}
fun body(body: String): Builder {
this.body = body
return this
}
fun addParent(parent: Plaintext?): Builder {
if (parent != null) {
val iv = parent.inventoryVector
if (iv == null) {
LOG.debug("Ignored parent without IV")
} else {
parents.add(iv)
}
}
return this
}
fun addParent(iv: InventoryVector?): Builder {
if (iv != null) {
parents.add(iv)
}
return this
}
fun addFile(file: File?, disposition: Attachment.Disposition): Builder {
if (file != null) {
try {
files.add(Attachment.Builder()
.name(file.name)
.disposition(disposition)
.type(URLConnection.guessContentTypeFromStream(FileInputStream(file)))
.data(Files.readAllBytes(file.toPath()))
.build())
} catch (e: IOException) {
LOG.error(e.message, e)
}
}
return this
}
fun addFile(file: Attachment?): Builder {
if (file != null) {
files.add(file)
}
return this
}
fun build(): ExtendedEncoding {
return ExtendedEncoding(Message(subject!!, body!!, parents, files))
}
}
class Unpacker : ExtendedEncoding.Unpacker<Message> {
override val type: String = TYPE
override fun unpack(map: MPMap<MPString, MPType<*>>): Message {
val subject = str(map[mp("subject")]) ?: ""
val body = str(map[mp("body")]) ?: ""
val parents = LinkedList<InventoryVector>()
val files = LinkedList<Attachment>()
val mpParents = map[mp("parents")] as? MPArray<*>
for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) {
parents.add(InventoryVector.fromHash(
(parent as? MPBinary)?.value ?: continue
) ?: continue)
}
val mpFiles = map[mp("files")] as? MPArray<*>
for (item in mpFiles ?: emptyList<Any>()) {
if (item is MPMap<*, *>) {
val b = Attachment.Builder()
b.name(str(item[mp("name")])!!)
b.data(
bin(item[mp("data")] ?: continue) ?: continue
)
b.type(str(item[mp("type")])!!)
val disposition = str(item[mp("disposition")])
if ("inline" == disposition) {
b.inline()
} else if ("attachment" == disposition) {
b.attachment()
}
files.add(b.build())
}
}
return Message(subject, body, parents, files)
}
private fun bin(data: MPType<*>): ByteArray? {
return (data as? MPBinary)?.value
}
}
companion object {
private val LOG = LoggerFactory.getLogger(Message::class.java)
val TYPE = "message"
}
}

View File

@ -1,114 +0,0 @@
package ch.dissem.bitmessage.entity.valueobject.extended;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.msgpack.types.*;
import java.io.IOException;
import java.util.Objects;
import static ch.dissem.bitmessage.utils.Strings.str;
import static ch.dissem.msgpack.types.Utils.mp;
/**
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
*/
public class Vote implements ExtendedEncoding.ExtendedType {
private static final long serialVersionUID = -8427038604209964837L;
public static final String TYPE = "vote";
private InventoryVector msgId;
private String vote;
private Vote(Builder builder) {
msgId = builder.msgId;
vote = builder.vote;
}
@Override
public String getType() {
return TYPE;
}
public InventoryVector getMsgId() {
return msgId;
}
public String getVote() {
return vote;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Vote vote1 = (Vote) o;
return Objects.equals(msgId, vote1.msgId) &&
Objects.equals(vote, vote1.vote);
}
@Override
public int hashCode() {
return Objects.hash(msgId, vote);
}
@Override
public MPMap<MPString, MPType<?>> pack() throws IOException {
MPMap<MPString, MPType<?>> result = new MPMap<>();
result.put(mp(""), mp(TYPE));
result.put(mp("msgId"), mp(msgId.getHash()));
result.put(mp("vote"), mp(vote));
return result;
}
public static class Builder {
private InventoryVector msgId;
private String vote;
public ExtendedEncoding up(Plaintext message) {
msgId = message.getInventoryVector();
vote = "1";
return new ExtendedEncoding(new Vote(this));
}
public ExtendedEncoding down(Plaintext message) {
msgId = message.getInventoryVector();
vote = "1";
return new ExtendedEncoding(new Vote(this));
}
public Builder msgId(InventoryVector iv) {
this.msgId = iv;
return this;
}
public Builder vote(String vote) {
this.vote = vote;
return this;
}
public ExtendedEncoding build() {
return new ExtendedEncoding(new Vote(this));
}
}
public static class Unpacker implements ExtendedEncoding.Unpacker<Vote> {
@Override
public String getType() {
return TYPE;
}
@Override
public Vote unpack(MPMap<MPString, MPType<?>> map) {
Vote.Builder builder = new Vote.Builder();
MPType<?> msgId = map.get(mp("msgId"));
if (msgId instanceof MPBinary) {
builder.msgId(InventoryVector.fromHash(((MPBinary) msgId).getValue()));
}
builder.vote(str(map.get(mp("vote"))));
return new Vote(builder);
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2017 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.valueobject.extended
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.utils.Strings.str
import ch.dissem.msgpack.types.MPBinary
import ch.dissem.msgpack.types.MPMap
import ch.dissem.msgpack.types.MPString
import ch.dissem.msgpack.types.MPType
import ch.dissem.msgpack.types.Utils.mp
/**
* Extended encoding type 'vote'. Specification still outstanding, so this will need some work.
*/
data class Vote constructor(val msgId: InventoryVector, val vote: String) : ExtendedEncoding.ExtendedType {
override val type: String = TYPE
override fun pack(): MPMap<MPString, MPType<*>> {
val result = MPMap<MPString, MPType<*>>()
result.put(mp(""), mp(TYPE))
result.put(mp("msgId"), mp(*msgId.hash))
result.put(mp("vote"), mp(vote))
return result
}
class Builder {
private var msgId: InventoryVector? = null
private var vote: String? = null
fun up(message: Plaintext): ExtendedEncoding {
msgId = message.inventoryVector
vote = "1"
return ExtendedEncoding(Vote(msgId!!, vote!!))
}
fun down(message: Plaintext): ExtendedEncoding {
msgId = message.inventoryVector
vote = "-1"
return ExtendedEncoding(Vote(msgId!!, vote!!))
}
fun msgId(iv: InventoryVector): Builder {
this.msgId = iv
return this
}
fun vote(vote: String): Builder {
this.vote = vote
return this
}
fun build(): ExtendedEncoding {
return ExtendedEncoding(Vote(msgId!!, vote!!))
}
}
class Unpacker : ExtendedEncoding.Unpacker<Vote> {
override val type: String
get() = TYPE
override fun unpack(map: MPMap<MPString, MPType<*>>): Vote {
val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId")
val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given")
return Vote(msgId, vote)
}
}
companion object {
@JvmField val TYPE = "vote"
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,15 +14,9 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.exception;
package ch.dissem.bitmessage.exception
/**
* Indicates an illegal Bitmessage address
*/
public class AddressFormatException extends RuntimeException {
private static final long serialVersionUID = 6943764578672021573L;
public AddressFormatException(String message) {
super(message);
}
}
class AddressFormatException(message: String) : RuntimeException(message)

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,14 +14,15 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.ports;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.MessagePayload;
package ch.dissem.bitmessage.exception
/**
* @author Christian Basler
*/
public interface CustomCommandHandler {
MessagePayload handle(CustomMessage request);
class ApplicationException : RuntimeException {
constructor(cause: Throwable) : super(cause)
constructor(message: String) : super(message)
}

View File

@ -0,0 +1,19 @@
/*
* Copyright 2017 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.exception
class DecryptionFailedException : Exception()

View File

@ -1,30 +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.exception;
import ch.dissem.bitmessage.utils.Strings;
import java.io.IOException;
import java.util.Arrays;
public class InsufficientProofOfWorkException extends IOException {
private static final long serialVersionUID = 9105580366564571318L;
public InsufficientProofOfWorkException(byte[] target, byte[] hash) {
super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.");
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright 2017 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.exception
import ch.dissem.bitmessage.utils.Strings
import java.io.IOException
import java.util.*
class InsufficientProofOfWorkException(target: ByteArray, hash: ByteArray) : IOException(
"Insufficient proof of work: " + Strings.hex(target) + " required, "
+ Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.")

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,21 +14,13 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.exception;
package ch.dissem.bitmessage.exception
/**
* An exception on the node that's severe enough to cause the client to disconnect this node.
*
* @author Ch. Basler
*/
public class NodeException extends RuntimeException {
private static final long serialVersionUID = 2965325796118227802L;
public NodeException(String message) {
super(message);
}
public NodeException(String message, Throwable cause) {
super(message, cause);
}
class NodeException(message: String?, cause: Throwable? = null) : RuntimeException(message ?: cause?.message, cause) {
constructor(message: String) : this(message, null)
}

View File

@ -1,92 +0,0 @@
/*
* Copyright 2016 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.factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;
import static ch.dissem.bitmessage.ports.NetworkHandler.HEADER_SIZE;
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
/**
* A pool for {@link ByteBuffer}s. As they may use up a lot of memory,
* they should be reused as efficiently as possible.
*/
class BufferPool {
private static final Logger LOG = LoggerFactory.getLogger(BufferPool.class);
public static final BufferPool bufferPool = new BufferPool();
private final Map<Integer, Stack<ByteBuffer>> pools = new TreeMap<>();
private BufferPool() {
pools.put(HEADER_SIZE, new Stack<ByteBuffer>());
pools.put(54, new Stack<ByteBuffer>());
pools.put(1000, new Stack<ByteBuffer>());
pools.put(60000, new Stack<ByteBuffer>());
pools.put(MAX_PAYLOAD_SIZE, new Stack<ByteBuffer>());
}
public synchronized ByteBuffer allocate(int capacity) {
Integer targetSize = getTargetSize(capacity);
Stack<ByteBuffer> pool = pools.get(targetSize);
if (pool.isEmpty()) {
LOG.trace("Creating new buffer of size " + targetSize);
return ByteBuffer.allocate(targetSize);
} else {
return pool.pop();
}
}
/**
* Returns a buffer that has the size of the Bitmessage network message header, 24 bytes.
*
* @return a buffer of size 24
*/
public synchronized ByteBuffer allocateHeaderBuffer() {
Stack<ByteBuffer> pool = pools.get(HEADER_SIZE);
if (pool.isEmpty()) {
return ByteBuffer.allocate(HEADER_SIZE);
} else {
return pool.pop();
}
}
public synchronized void deallocate(ByteBuffer buffer) {
buffer.clear();
Stack<ByteBuffer> pool = pools.get(buffer.capacity());
if (pool == null) {
throw new IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() +
" one of " + pools.keySet() + " expected.");
} else {
pool.push(buffer);
}
}
private Integer getTargetSize(int capacity) {
for (Integer size : pools.keySet()) {
if (size >= capacity) return size;
}
throw new IllegalArgumentException("Requested capacity too large: " +
"requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE);
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright 2017 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.factory
import ch.dissem.bitmessage.constants.Network.HEADER_SIZE
import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE
import org.slf4j.LoggerFactory
import java.nio.ByteBuffer
import java.util.*
/**
* A pool for [ByteBuffer]s. As they may use up a lot of memory,
* they should be reused as efficiently as possible.
*/
object BufferPool {
private val LOG = LoggerFactory.getLogger(BufferPool::class.java)
private val pools = mapOf(
HEADER_SIZE to Stack<ByteBuffer>(),
54 to Stack<ByteBuffer>(),
1000 to Stack<ByteBuffer>(),
60000 to Stack<ByteBuffer>(),
MAX_PAYLOAD_SIZE to Stack<ByteBuffer>()
)
@Synchronized fun allocate(capacity: Int): ByteBuffer {
val targetSize = getTargetSize(capacity)
val pool = pools[targetSize]
if (pool == null || pool.isEmpty()) {
LOG.trace("Creating new buffer of size " + targetSize!!)
return ByteBuffer.allocate(targetSize)
} else {
return pool.pop()
}
}
/**
* Returns a buffer that has the size of the Bitmessage network message header, 24 bytes.
* @return a buffer of size 24
*/
@Synchronized fun allocateHeaderBuffer(): ByteBuffer {
val pool = pools[HEADER_SIZE]
if (pool == null || pool.isEmpty()) {
return ByteBuffer.allocate(HEADER_SIZE)
} else {
return pool.pop()
}
}
@Synchronized fun deallocate(buffer: ByteBuffer) {
buffer.clear()
val pool = pools[buffer.capacity()]
pool?.push(buffer) ?: throw IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() +
" one of " + pools.keys + " expected.")
}
private fun getTargetSize(capacity: Int): Int? {
for (size in pools.keys) {
if (size >= capacity) return size
}
throw IllegalArgumentException("Requested capacity too large: " +
"requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE)
}
}

View File

@ -1,62 +0,0 @@
package ch.dissem.bitmessage.factory;
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding;
import ch.dissem.bitmessage.entity.valueobject.extended.Message;
import ch.dissem.bitmessage.entity.valueobject.extended.Vote;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.msgpack.Reader;
import ch.dissem.msgpack.types.MPMap;
import ch.dissem.msgpack.types.MPString;
import ch.dissem.msgpack.types.MPType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.InflaterInputStream;
import static ch.dissem.bitmessage.utils.Strings.str;
/**
* Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a
* {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}.
*/
public class ExtendedEncodingFactory {
private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class);
private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory();
private static final MPString KEY_MESSAGE_TYPE = new MPString("");
private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>();
private ExtendedEncodingFactory() {
registerFactory(new Message.Unpacker());
registerFactory(new Vote.Unpacker());
}
public void registerFactory(ExtendedEncoding.Unpacker<?> factory) {
factories.put(factory.getType(), factory);
}
public ExtendedEncoding unzip(byte[] zippedData) {
try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) {
Reader reader = Reader.getInstance();
@SuppressWarnings("unchecked")
MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper);
MPType<?> messageType = map.get(KEY_MESSAGE_TYPE);
if (messageType == null) {
LOG.error("Missing message type");
return null;
}
ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType));
return new ExtendedEncoding(factory.unpack(map));
} catch (ClassCastException | IOException e) {
throw new ApplicationException(e);
}
}
public static ExtendedEncodingFactory getInstance() {
return INSTANCE;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright 2017 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.factory
import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding
import ch.dissem.bitmessage.entity.valueobject.extended.Message
import ch.dissem.bitmessage.entity.valueobject.extended.Vote
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.utils.Strings.str
import ch.dissem.msgpack.Reader
import ch.dissem.msgpack.types.MPMap
import ch.dissem.msgpack.types.MPString
import ch.dissem.msgpack.types.MPType
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.util.*
import java.util.zip.InflaterInputStream
/**
* Factory that creates [ExtendedEncoding] objects from byte arrays. You can register your own types by adding a
* [ExtendedEncoding.Unpacker] using [.registerFactory].
*/
object ExtendedEncodingFactory {
private val LOG = LoggerFactory.getLogger(ExtendedEncodingFactory::class.java)
private val KEY_MESSAGE_TYPE = MPString("")
private val factories = HashMap<String, ExtendedEncoding.Unpacker<*>>()
init {
registerFactory(Message.Unpacker())
registerFactory(Vote.Unpacker())
}
fun registerFactory(factory: ExtendedEncoding.Unpacker<*>) {
factories.put(factory.type, factory)
}
fun unzip(zippedData: ByteArray): ExtendedEncoding? {
try {
InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper ->
val reader = Reader.getInstance()
@Suppress("UNCHECKED_CAST")
val map = reader.read(unzipper) as MPMap<MPString, MPType<*>>
val messageType = map[KEY_MESSAGE_TYPE]
if (messageType == null) {
LOG.error("Missing message type")
return null
}
val factory = factories[str(messageType)]
return ExtendedEncoding(
factory?.unpack(map) ?: return null
)
}
} catch (e: ClassCastException) {
throw ApplicationException(e)
}
}
}

View File

@ -1,217 +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.factory;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.exception.NodeException;
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.io.InputStream;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
*/
public class Factory {
private static final Logger LOG = LoggerFactory.getLogger(Factory.class);
public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException {
try {
return V3MessageFactory.read(stream);
} catch (SocketTimeoutException | NodeException e) {
throw e;
} catch (SocketException e) {
throw new NodeException(e.getMessage(), e);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
return null;
}
}
public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) {
try {
return V3MessageFactory.readObject(stream, length);
} catch (IOException e) {
LOG.error(e.getMessage(), e);
return null;
}
}
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
Pubkey.Feature.bitfield(features));
}
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
+ publicSigningKey.length + " bytes long.");
if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65)
throw new IllegalArgumentException("64 bytes encryption key expected, but it was "
+ publicEncryptionKey.length + " bytes long.");
switch ((int) version) {
case 2:
return new V2Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.build();
case 3:
return new V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build();
case 4:
return new V4Pubkey(
new V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build()
);
default:
throw new IllegalArgumentException("Unexpected pubkey version " + version);
}
}
public static BitmessageAddress createIdentityFromPrivateKey(String address,
byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes,
int behaviourBitfield) {
BitmessageAddress temp = new BitmessageAddress(address);
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
createPubkey(temp.getVersion(), temp.getStream(),
cryptography().createPublicKey(privateSigningKey),
cryptography().createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, behaviourBitfield));
BitmessageAddress result = new BitmessageAddress(privateKey);
if (!result.getAddress().equals(address)) {
throw new IllegalArgumentException("Address not matching private key. Address: " + address
+ "; Address derived from private key: " + result.getAddress());
}
return result;
}
public static BitmessageAddress generatePrivateAddress(boolean shorter,
long stream,
Pubkey.Feature... features) {
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
}
static ObjectPayload getObjectPayload(long objectType,
long version,
long streamNumber,
InputStream stream,
int length) throws IOException {
ObjectType type = ObjectType.fromNumber(objectType);
if (type != null) {
switch (type) {
case GET_PUBKEY:
return parseGetPubkey(version, streamNumber, stream, length);
case PUBKEY:
return parsePubkey(version, streamNumber, stream, length);
case MSG:
return parseMsg(version, streamNumber, stream, length);
case BROADCAST:
return parseBroadcast(version, streamNumber, stream, length);
default:
LOG.error("This should not happen, someone broke something in the code!");
}
}
// fallback: just store the message - we don't really care what it is
LOG.trace("Unexpected object type: " + objectType);
return GenericPayload.read(version, streamNumber, stream, length);
}
private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
return GetPubkey.read(stream, streamNumber, length, version);
}
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
switch ((int) version) {
case 2:
return V2Pubkey.read(is, stream);
case 3:
return V3Pubkey.read(is, stream);
case 4:
return V4Pubkey.read(is, stream, length, encrypted);
}
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
return null;
}
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
return pubkey != null ? pubkey : GenericPayload.read(version, streamNumber, stream, length);
}
private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException {
return Msg.read(stream, streamNumber, length);
}
private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException {
switch ((int) version) {
case 4:
return V4Broadcast.read(stream, streamNumber, length);
case 5:
return V5Broadcast.read(stream, streamNumber, length);
default:
LOG.debug("Encountered unknown broadcast version " + version);
return GenericPayload.read(version, streamNumber, stream, length);
}
}
public static Broadcast getBroadcast(Plaintext plaintext) {
BitmessageAddress sendingAddress = plaintext.getFrom();
if (sendingAddress.getVersion() < 4) {
return new V4Broadcast(sendingAddress, plaintext);
} else {
return new V5Broadcast(sendingAddress, plaintext);
}
}
public static ObjectMessage createAck(Plaintext plaintext) {
if (plaintext == null || plaintext.getAckData() == null)
return null;
GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData());
return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build();
}
}

View File

@ -0,0 +1,206 @@
/*
* Copyright 2017 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.factory
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.NetworkMessage
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.payload.*
import ch.dissem.bitmessage.entity.payload.ObjectType.MSG
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.utils.Singleton.cryptography
import ch.dissem.bitmessage.utils.UnixTime
import org.slf4j.LoggerFactory
import java.io.IOException
import java.io.InputStream
import java.net.SocketException
import java.net.SocketTimeoutException
/**
* Creates [NetworkMessage] objects from [InputStreams][InputStream]
*/
object Factory {
private val LOG = LoggerFactory.getLogger(Factory::class.java)
@Throws(SocketTimeoutException::class)
@JvmStatic fun getNetworkMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream): NetworkMessage? {
try {
return V3MessageFactory.read(stream)
} catch (e: Exception) {
when (e) {
is SocketTimeoutException,
is NodeException -> throw e
is SocketException -> throw NodeException(e.message, e)
else -> {
LOG.error(e.message, e)
return null
}
}
}
}
@JvmStatic fun getObjectMessage(version: Int, stream: InputStream, length: Int): ObjectMessage? {
try {
return V3MessageFactory.readObject(stream, length)
} catch (e: IOException) {
LOG.error(e.message, e)
return null
}
}
@JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
Pubkey.Feature.bitfield(*features))
}
@JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long, behaviourBitfield: Int): Pubkey {
if (publicSigningKey.size != 64 && publicSigningKey.size != 65)
throw IllegalArgumentException("64 bytes signing key expected, but it was "
+ publicSigningKey.size + " bytes long.")
if (publicEncryptionKey.size != 64 && publicEncryptionKey.size != 65)
throw IllegalArgumentException("64 bytes encryption key expected, but it was "
+ publicEncryptionKey.size + " bytes long.")
when (version.toInt()) {
2 -> return V2Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.build()
3 -> return V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build()
4 -> return V4Pubkey(
V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build()
)
else -> throw IllegalArgumentException("Unexpected pubkey version " + version)
}
}
@JvmStatic fun createIdentityFromPrivateKey(address: String,
privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long,
behaviourBitfield: Int): BitmessageAddress {
val temp = BitmessageAddress(address)
val privateKey = PrivateKey(privateSigningKey, privateEncryptionKey,
createPubkey(temp.version, temp.stream,
cryptography().createPublicKey(privateSigningKey),
cryptography().createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, behaviourBitfield))
val result = BitmessageAddress(privateKey)
if (result.address != address) {
throw IllegalArgumentException("Address not matching private key. Address: " + address
+ "; Address derived from private key: " + result.address)
}
return result
}
@JvmStatic fun generatePrivateAddress(shorter: Boolean,
stream: Long,
vararg features: Pubkey.Feature): BitmessageAddress {
return BitmessageAddress(PrivateKey(shorter, stream, 1000, 1000, *features))
}
@JvmStatic fun getObjectPayload(objectType: Long,
version: Long,
streamNumber: Long,
stream: InputStream,
length: Int): ObjectPayload {
val type = ObjectType.fromNumber(objectType)
if (type != null) {
when (type) {
ObjectType.GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length)
ObjectType.PUBKEY -> return parsePubkey(version, streamNumber, stream, length)
MSG -> return parseMsg(version, streamNumber, stream, length)
ObjectType.BROADCAST -> return parseBroadcast(version, streamNumber, stream, length)
else -> LOG.error("This should not happen, someone broke something in the code!")
}
}
// fallback: just store the message - we don't really care what it is
LOG.trace("Unexpected object type: " + objectType)
return GenericPayload.read(version, streamNumber, stream, length)
}
@JvmStatic private fun parseGetPubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
return GetPubkey.read(stream, streamNumber, length, version)
}
@JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? {
when (version.toInt()) {
2 -> return V2Pubkey.read(`is`, stream)
3 -> return V3Pubkey.read(`is`, stream)
4 -> return V4Pubkey.read(`is`, stream, length, encrypted)
}
LOG.debug("Unexpected pubkey version $version, handling as generic payload object")
return null
}
@JvmStatic private fun parsePubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
val pubkey = readPubkey(version, streamNumber, stream, length, true)
return pubkey ?: GenericPayload.read(version, streamNumber, stream, length)
}
@JvmStatic private fun parseMsg(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
return Msg.read(stream, streamNumber, length)
}
@JvmStatic private fun parseBroadcast(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload {
when (version.toInt()) {
4 -> return V4Broadcast.read(stream, streamNumber, length)
5 -> return V5Broadcast.read(stream, streamNumber, length)
else -> {
LOG.debug("Encountered unknown broadcast version " + version)
return GenericPayload.read(version, streamNumber, stream, length)
}
}
}
@JvmStatic fun getBroadcast(plaintext: Plaintext): Broadcast {
val sendingAddress = plaintext.from
if (sendingAddress.version < 4) {
return V4Broadcast(sendingAddress, plaintext)
} else {
return V5Broadcast(sendingAddress, plaintext)
}
}
@JvmStatic fun createAck(from: BitmessageAddress, ackData: ByteArray?, ttl: Long): ObjectMessage? {
val ack = GenericPayload(
3, from.stream,
ackData ?: return null
)
return ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now + ttl).build()
}
}

View File

@ -1,236 +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.factory;
import ch.dissem.bitmessage.entity.*;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.utils.AccessCounter;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Creates protocol v3 network messages from {@link InputStream InputStreams}
*/
class V3MessageFactory {
private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class);
public static NetworkMessage read(InputStream in) throws IOException {
findMagic(in);
String command = getCommand(in);
int length = (int) Decode.uint32(in);
if (length > 1600003) {
throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected.");
}
byte[] checksum = Decode.bytes(in, 4);
byte[] payloadBytes = Decode.bytes(in, length);
if (testChecksum(checksum, payloadBytes)) {
MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length);
if (payload != null)
return new NetworkMessage(payload);
else
return null;
} else {
throw new IOException("Checksum failed for message '" + command + "'");
}
}
static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException {
switch (command) {
case "version":
return parseVersion(stream);
case "verack":
return new VerAck();
case "addr":
return parseAddr(stream);
case "inv":
return parseInv(stream);
case "getdata":
return parseGetData(stream);
case "object":
return readObject(stream, length);
case "custom":
return readCustom(stream, length);
default:
LOG.debug("Unknown command: " + command);
return null;
}
}
private static MessagePayload readCustom(InputStream in, int length) throws IOException {
return CustomMessage.read(in, length);
}
public static ObjectMessage readObject(InputStream in, int length) throws IOException {
AccessCounter counter = new AccessCounter();
byte nonce[] = Decode.bytes(in, 8, counter);
long expiresTime = Decode.int64(in, counter);
long objectType = Decode.uint32(in, counter);
long version = Decode.varInt(in, counter);
long stream = Decode.varInt(in, counter);
byte[] data = Decode.bytes(in, length - counter.length());
ObjectPayload payload;
try {
ByteArrayInputStream dataStream = new ByteArrayInputStream(data);
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length);
} catch (Exception e) {
if (LOG.isTraceEnabled()) {
LOG.trace("Could not parse object payload - using generic payload instead", e);
LOG.trace(Strings.hex(data).toString());
}
payload = new GenericPayload(version, stream, data);
}
return new ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.stream(stream)
.payload(payload)
.build();
}
private static GetData parseGetData(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
GetData.Builder builder = new GetData.Builder();
for (int i = 0; i < count; i++) {
builder.addInventoryVector(parseInventoryVector(stream));
}
return builder.build();
}
private static Inv parseInv(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
Inv.Builder builder = new Inv.Builder();
for (int i = 0; i < count; i++) {
builder.addInventoryVector(parseInventoryVector(stream));
}
return builder.build();
}
private static Addr parseAddr(InputStream stream) throws IOException {
long count = Decode.varInt(stream);
Addr.Builder builder = new Addr.Builder();
for (int i = 0; i < count; i++) {
builder.addAddress(parseAddress(stream, false));
}
return builder.build();
}
private static Version parseVersion(InputStream stream) throws IOException {
int version = Decode.int32(stream);
long services = Decode.int64(stream);
long timestamp = Decode.int64(stream);
NetworkAddress addrRecv = parseAddress(stream, true);
NetworkAddress addrFrom = parseAddress(stream, true);
long nonce = Decode.int64(stream);
String userAgent = Decode.varString(stream);
long[] streamNumbers = Decode.varIntList(stream);
return new Version.Builder()
.version(version)
.services(services)
.timestamp(timestamp)
.addrRecv(addrRecv).addrFrom(addrFrom)
.nonce(nonce)
.userAgent(userAgent)
.streams(streamNumbers).build();
}
private static InventoryVector parseInventoryVector(InputStream stream) throws IOException {
return InventoryVector.fromHash(Decode.bytes(stream, 32));
}
private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException {
long time;
long streamNumber;
if (!light) {
time = Decode.int64(stream);
streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct
} else {
time = 0;
streamNumber = 0;
}
long services = Decode.int64(stream);
byte[] ipv6 = Decode.bytes(stream, 16);
int port = Decode.uint16(stream);
return new NetworkAddress.Builder()
.time(time)
.stream(streamNumber)
.services(services)
.ipv6(ipv6)
.port(port)
.build();
}
private static boolean testChecksum(byte[] checksum, byte[] payload) {
byte[] payloadChecksum = cryptography().sha512(payload);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
}
}
return true;
}
private static String getCommand(InputStream stream) throws IOException {
byte[] bytes = new byte[12];
int end = bytes.length;
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) stream.read();
if (end == bytes.length) {
if (bytes[i] == 0) end = i;
} else {
if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command");
}
}
return new String(bytes, 0, end, "ASCII");
}
private static void findMagic(InputStream in) throws IOException {
int pos = 0;
for (int i = 0; i < 1620000; i++) {
byte b = (byte) in.read();
if (b == MAGIC_BYTES[pos]) {
if (pos + 1 == MAGIC_BYTES.length) {
return;
}
} else if (pos > 0 && b == MAGIC_BYTES[0]) {
pos = 1;
} else {
pos = 0;
}
pos++;
}
throw new NodeException("Failed to find MAGIC bytes in stream");
}
}

View File

@ -0,0 +1,230 @@
/*
* Copyright 2017 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.factory
import ch.dissem.bitmessage.entity.*
import ch.dissem.bitmessage.entity.payload.GenericPayload
import ch.dissem.bitmessage.entity.payload.ObjectPayload
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress
import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.utils.AccessCounter
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import ch.dissem.bitmessage.utils.Strings
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.util.*
/**
* Creates protocol v3 network messages from [InputStreams][InputStream]
*/
object V3MessageFactory {
private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java)
@JvmStatic
fun read(`in`: InputStream): NetworkMessage? {
findMagic(`in`)
val command = getCommand(`in`)
val length = Decode.uint32(`in`).toInt()
if (length > 1600003) {
throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.")
}
val checksum = Decode.bytes(`in`, 4)
val payloadBytes = Decode.bytes(`in`, length)
if (testChecksum(checksum, payloadBytes)) {
val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length)
if (payload != null)
return NetworkMessage(payload)
else
return null
} else {
throw IOException("Checksum failed for message '$command'")
}
}
@JvmStatic
fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? {
when (command) {
"version" -> return parseVersion(stream)
"verack" -> return VerAck()
"addr" -> return parseAddr(stream)
"inv" -> return parseInv(stream)
"getdata" -> return parseGetData(stream)
"object" -> return readObject(stream, length)
"custom" -> return readCustom(stream, length)
else -> {
LOG.debug("Unknown command: " + command)
return null
}
}
}
private fun readCustom(`in`: InputStream, length: Int): MessagePayload {
return CustomMessage.read(`in`, length)
}
@JvmStatic
fun readObject(`in`: InputStream, length: Int): ObjectMessage {
val counter = AccessCounter()
val nonce = Decode.bytes(`in`, 8, counter)
val expiresTime = Decode.int64(`in`, counter)
val objectType = Decode.uint32(`in`, counter)
val version = Decode.varInt(`in`, counter)
val stream = Decode.varInt(`in`, counter)
val data = Decode.bytes(`in`, length - counter.length())
var payload: ObjectPayload
try {
val dataStream = ByteArrayInputStream(data)
payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size)
} catch (e: Exception) {
if (LOG.isTraceEnabled) {
LOG.trace("Could not parse object payload - using generic payload instead", e)
LOG.trace(Strings.hex(data).toString())
}
payload = GenericPayload(version, stream, data)
}
return ObjectMessage.Builder()
.nonce(nonce)
.expiresTime(expiresTime)
.objectType(objectType)
.stream(stream)
.payload(payload)
.build()
}
private fun parseGetData(stream: InputStream): GetData {
val count = Decode.varInt(stream)
val inventoryVectors = LinkedList<InventoryVector>()
for (i in 0..count - 1) {
inventoryVectors.add(parseInventoryVector(stream))
}
return GetData(inventoryVectors)
}
private fun parseInv(stream: InputStream): Inv {
val count = Decode.varInt(stream)
val inventoryVectors = LinkedList<InventoryVector>()
for (i in 0..count - 1) {
inventoryVectors.add(parseInventoryVector(stream))
}
return Inv(inventoryVectors)
}
private fun parseAddr(stream: InputStream): Addr {
val count = Decode.varInt(stream)
val networkAddresses = LinkedList<NetworkAddress>()
for (i in 0..count - 1) {
networkAddresses.add(parseAddress(stream, false))
}
return Addr(networkAddresses)
}
private fun parseVersion(stream: InputStream): Version {
val version = Decode.int32(stream)
val services = Decode.int64(stream)
val timestamp = Decode.int64(stream)
val addrRecv = parseAddress(stream, true)
val addrFrom = parseAddress(stream, true)
val nonce = Decode.int64(stream)
val userAgent = Decode.varString(stream)
val streamNumbers = Decode.varIntList(stream)
return Version.Builder()
.version(version)
.services(services)
.timestamp(timestamp)
.addrRecv(addrRecv).addrFrom(addrFrom)
.nonce(nonce)
.userAgent(userAgent)
.streams(*streamNumbers).build()
}
private fun parseInventoryVector(stream: InputStream): InventoryVector {
return InventoryVector(Decode.bytes(stream, 32))
}
private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress {
val time: Long
val streamNumber: Long
if (!light) {
time = Decode.int64(stream)
streamNumber = Decode.uint32(stream) // This isn't consistent, not sure if this is correct
} else {
time = 0
streamNumber = 0
}
val services = Decode.int64(stream)
val ipv6 = Decode.bytes(stream, 16)
val port = Decode.uint16(stream)
return NetworkAddress.Builder()
.time(time)
.stream(streamNumber)
.services(services)
.ipv6(ipv6)
.port(port)
.build()
}
private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean {
val payloadChecksum = cryptography().sha512(payload)
for (i in checksum.indices) {
if (checksum[i] != payloadChecksum[i]) {
return false
}
}
return true
}
private fun getCommand(stream: InputStream): String {
val bytes = ByteArray(12)
var end = bytes.size
for (i in bytes.indices) {
bytes[i] = stream.read().toByte()
if (end == bytes.size) {
if (bytes[i].toInt() == 0) end = i
} else {
if (bytes[i].toInt() != 0) throw IOException("'\\u0000' padding expected for command")
}
}
return String(bytes, 0, end, Charsets.US_ASCII)
}
private fun findMagic(`in`: InputStream) {
var pos = 0
for (i in 0..1619999) {
val b = `in`.read().toByte()
if (b == NetworkMessage.MAGIC_BYTES[pos]) {
if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) {
return
}
} else if (pos > 0 && b == NetworkMessage.MAGIC_BYTES[0]) {
pos = 1
} else {
pos = 0
}
pos++
}
throw NodeException("Failed to find MAGIC bytes in stream")
}
}

View File

@ -1,189 +0,0 @@
/*
* Copyright 2016 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.factory;
import ch.dissem.bitmessage.entity.MessagePayload;
import ch.dissem.bitmessage.entity.NetworkMessage;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.exception.NodeException;
import ch.dissem.bitmessage.utils.Decode;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
import static ch.dissem.bitmessage.factory.BufferPool.bufferPool;
import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE;
import static ch.dissem.bitmessage.utils.Singleton.cryptography;
/**
* Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message.
*/
public class V3MessageReader {
private ByteBuffer headerBuffer;
private ByteBuffer dataBuffer;
private ReaderState state = ReaderState.MAGIC;
private String command;
private int length;
private byte[] checksum;
private List<NetworkMessage> messages = new LinkedList<>();
public ByteBuffer getActiveBuffer() {
if (state != null && state != ReaderState.DATA) {
if (headerBuffer == null) {
headerBuffer = bufferPool.allocateHeaderBuffer();
}
}
return state == ReaderState.DATA ? dataBuffer : headerBuffer;
}
public void update() {
if (state != ReaderState.DATA) {
getActiveBuffer();
headerBuffer.flip();
}
switch (state) {
case MAGIC:
if (!findMagicBytes(headerBuffer)) {
headerBuffer.compact();
return;
}
state = ReaderState.HEADER;
case HEADER:
if (headerBuffer.remaining() < 20) {
headerBuffer.compact();
headerBuffer.limit(20);
return;
}
command = getCommand(headerBuffer);
length = (int) Decode.uint32(headerBuffer);
if (length > MAX_PAYLOAD_SIZE) {
throw new NodeException("Payload of " + length + " bytes received, no more than " +
MAX_PAYLOAD_SIZE + " was expected.");
}
checksum = new byte[4];
headerBuffer.get(checksum);
state = ReaderState.DATA;
bufferPool.deallocate(headerBuffer);
headerBuffer = null;
dataBuffer = bufferPool.allocate(length);
dataBuffer.clear();
dataBuffer.limit(length);
case DATA:
if (dataBuffer.position() < length) {
return;
} else {
dataBuffer.flip();
}
if (!testChecksum(dataBuffer)) {
state = ReaderState.MAGIC;
throw new NodeException("Checksum failed for message '" + command + "'");
}
try {
MessagePayload payload = V3MessageFactory.getPayload(
command,
new ByteArrayInputStream(dataBuffer.array(),
dataBuffer.arrayOffset() + dataBuffer.position(), length),
length);
if (payload != null) {
messages.add(new NetworkMessage(payload));
}
} catch (IOException e) {
throw new NodeException(e.getMessage());
} finally {
state = ReaderState.MAGIC;
bufferPool.deallocate(dataBuffer);
dataBuffer = null;
dataBuffer = null;
}
}
}
public List<NetworkMessage> getMessages() {
return messages;
}
private boolean findMagicBytes(ByteBuffer buffer) {
int i = 0;
while (buffer.hasRemaining()) {
if (i == 0) {
buffer.mark();
}
if (buffer.get() == MAGIC_BYTES[i]) {
i++;
if (i == MAGIC_BYTES.length) {
return true;
}
} else {
i = 0;
}
}
if (i > 0) {
buffer.reset();
}
return false;
}
private static String getCommand(ByteBuffer buffer) {
int start = buffer.position();
int l = 0;
while (l < 12 && buffer.get() != 0) l++;
int i = l + 1;
while (i < 12) {
if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command");
i++;
}
try {
return new String(buffer.array(), start, l, "ASCII");
} catch (UnsupportedEncodingException e) {
throw new ApplicationException(e);
}
}
private boolean testChecksum(ByteBuffer buffer) {
byte[] payloadChecksum = cryptography().sha512(buffer.array(),
buffer.arrayOffset() + buffer.position(), length);
for (int i = 0; i < checksum.length; i++) {
if (checksum[i] != payloadChecksum[i]) {
return false;
}
}
return true;
}
/**
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
* connection is severed.
*/
public void cleanup() {
state = null;
if (headerBuffer != null) {
bufferPool.deallocate(headerBuffer);
}
if (dataBuffer != null) {
bufferPool.deallocate(dataBuffer);
}
}
private enum ReaderState {MAGIC, HEADER, DATA}
}

View File

@ -0,0 +1,190 @@
/*
* Copyright 2017 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.factory
import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE
import ch.dissem.bitmessage.entity.NetworkMessage
import ch.dissem.bitmessage.exception.NodeException
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.ByteArrayInputStream
import java.io.IOException
import java.nio.ByteBuffer
import java.util.*
/**
* Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message.
*/
class V3MessageReader {
private var headerBuffer: ByteBuffer? = null
private var dataBuffer: ByteBuffer? = null
private var state: ReaderState? = ReaderState.MAGIC
private var command: String? = null
private var length: Int = 0
private val checksum = ByteArray(4)
private val messages = LinkedList<NetworkMessage>()
val activeBuffer: ByteBuffer
get() {
if (state != null && state != ReaderState.DATA) {
if (headerBuffer == null) {
headerBuffer = BufferPool.allocateHeaderBuffer()
}
}
return if (state == ReaderState.DATA)
dataBuffer ?: throw IllegalStateException("data buffer is null")
else
headerBuffer ?: throw IllegalStateException("header buffer is null")
}
fun update() {
if (state != ReaderState.DATA) {
activeBuffer
headerBuffer!!.flip()
}
when (state) {
V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null"))
V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null"))
V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null"))
}
}
private fun magic(headerBuffer: ByteBuffer) {
if (!findMagicBytes(headerBuffer)) {
headerBuffer.compact()
return
} else {
state = ReaderState.HEADER
header(headerBuffer)
}
}
private fun header(headerBuffer: ByteBuffer) {
if (headerBuffer.remaining() < 20) {
headerBuffer.compact()
headerBuffer.limit(20)
return
}
command = getCommand(headerBuffer)
length = Decode.uint32(headerBuffer).toInt()
if (length > MAX_PAYLOAD_SIZE) {
throw NodeException("Payload of " + length + " bytes received, no more than " +
MAX_PAYLOAD_SIZE + " was expected.")
}
headerBuffer.get(checksum)
state = ReaderState.DATA
this.headerBuffer = null
BufferPool.deallocate(headerBuffer)
val dataBuffer = BufferPool.allocate(length)
this.dataBuffer = dataBuffer
data(dataBuffer)
}
private fun data(dataBuffer: ByteBuffer) {
dataBuffer.clear()
dataBuffer.limit(length)
if (dataBuffer.position() < length) {
return
} else {
dataBuffer.flip()
}
if (!testChecksum(dataBuffer)) {
state = ReaderState.MAGIC
this.dataBuffer = null
BufferPool.deallocate(dataBuffer)
throw NodeException("Checksum failed for message '$command'")
}
try {
V3MessageFactory.getPayload(
command ?: throw IllegalStateException("command is null"),
ByteArrayInputStream(dataBuffer.array(),
dataBuffer.arrayOffset() + dataBuffer.position(), length),
length
)?.let { messages.add(NetworkMessage(it)) }
} catch (e: IOException) {
throw NodeException(e.message)
} finally {
state = ReaderState.MAGIC
this.dataBuffer = null
BufferPool.deallocate(dataBuffer)
}
}
fun getMessages(): List<NetworkMessage> {
return messages
}
private fun findMagicBytes(buffer: ByteBuffer): Boolean {
var i = 0
while (buffer.hasRemaining()) {
if (i == 0) {
buffer.mark()
}
if (buffer.get() == NetworkMessage.MAGIC_BYTES[i]) {
i++
if (i == NetworkMessage.MAGIC_BYTES.size) {
return true
}
} else {
i = 0
}
}
if (i > 0) {
buffer.reset()
}
return false
}
private fun getCommand(buffer: ByteBuffer): String {
val start = buffer.position()
var l = 0
while (l < 12 && buffer.get().toInt() != 0) l++
var i = l + 1
while (i < 12) {
if (buffer.get().toInt() != 0) throw NodeException("'\\u0000' padding expected for command")
i++
}
return String(buffer.array(), start, l, Charsets.US_ASCII)
}
private fun testChecksum(buffer: ByteBuffer): Boolean {
val payloadChecksum = cryptography().sha512(buffer.array(),
buffer.arrayOffset() + buffer.position(), length)
for (i in checksum.indices) {
if (checksum[i] != payloadChecksum[i]) {
return false
}
}
return true
}
/**
* De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its
* connection is severed.
*/
fun cleanup() {
state = null
headerBuffer?.let { BufferPool.deallocate(it) }
dataBuffer?.let { BufferPool.deallocate(it) }
}
private enum class ReaderState {
MAGIC, HEADER, DATA
}
}

View File

@ -1,214 +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.ports;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
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.Numbers.max;
/**
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
*/
public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder {
protected static final Logger LOG = LoggerFactory.getLogger(Cryptography.class);
private static final SecureRandom RANDOM = new SecureRandom();
private static final BigInteger TWO = BigInteger.valueOf(2);
private static final BigInteger TWO_POW_64 = TWO.pow(64);
private static final BigInteger TWO_POW_16 = TWO.pow(16);
protected static final String ALGORITHM_ECDSA = "ECDSA";
protected static final String ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA";
protected static final String ALGORITHM_EVP_SHA256 = "SHA256withECDSA";
protected final Provider provider;
private InternalContext context;
protected AbstractCryptography(Provider provider) {
this.provider = provider;
}
@Override
public void setContext(InternalContext context) {
this.context = context;
}
public byte[] sha512(byte[] data, int offset, int length) {
MessageDigest mda = md("SHA-512");
mda.update(data, offset, length);
return mda.digest();
}
public byte[] sha512(byte[]... data) {
return hash("SHA-512", data);
}
public byte[] doubleSha512(byte[]... data) {
MessageDigest mda = md("SHA-512");
for (byte[] d : data) {
mda.update(d);
}
return mda.digest(mda.digest());
}
public byte[] doubleSha512(byte[] data, int length) {
MessageDigest mda = md("SHA-512");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
public byte[] ripemd160(byte[]... data) {
return hash("RIPEMD160", data);
}
public byte[] doubleSha256(byte[] data, int length) {
MessageDigest mda = md("SHA-256");
mda.update(data, 0, length);
return mda.digest(mda.digest());
}
public byte[] sha1(byte[]... data) {
return hash("SHA-1", data);
}
public byte[] randomBytes(int length) {
byte[] result = new byte[length];
RANDOM.nextBytes(result);
return result;
}
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes, ProofOfWorkEngine.Callback callback) {
nonceTrialsPerByte = max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE);
extraBytes = max(extraBytes, NETWORK_EXTRA_BYTES);
byte[] initialHash = getInitialHash(object);
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
}
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
throws IOException {
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
if (Bytes.lt(target, value, 8)) {
throw new InsufficientProofOfWorkException(target, value);
}
}
protected byte[] doSign(byte[] data, java.security.PrivateKey privKey) throws GeneralSecurityException {
// TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network
Signature sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider);
sig.initSign(privKey);
sig.update(data);
return sig.sign();
}
protected boolean doCheckSignature(byte[] data, byte[] signature, PublicKey publicKey) throws GeneralSecurityException {
for (String algorithm : new String[]{ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256}) {
Signature sig = Signature.getInstance(algorithm, provider);
sig.initVerify(publicKey);
sig.update(data);
if (sig.verify(signature)) {
return true;
}
}
return false;
}
@Override
public byte[] getInitialHash(ObjectMessage object) {
return sha512(object.getPayloadBytesWithoutNonce());
}
@Override
public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
if (nonceTrialsPerByte == 0) nonceTrialsPerByte = NETWORK_NONCE_TRIALS_PER_BYTE;
if (extraBytes == 0) extraBytes = NETWORK_EXTRA_BYTES;
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte)
.multiply(
powLength.add(
powLength.multiply(TTL).divide(TWO_POW_16)
)
);
return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8);
}
private byte[] hash(String algorithm, byte[]... data) {
MessageDigest mda = md(algorithm);
for (byte[] d : data) {
mda.update(d);
}
return mda.digest();
}
private MessageDigest md(String algorithm) {
try {
return MessageDigest.getInstance(algorithm, provider);
} catch (GeneralSecurityException e) {
throw new ApplicationException(e);
}
}
public byte[] mac(byte[] key_m, byte[] data) {
try {
Mac mac = Mac.getInstance("HmacSHA256", provider);
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
return mac.doFinal(data);
} catch (GeneralSecurityException e) {
throw new ApplicationException(e);
}
}
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return Factory.createPubkey(version, stream,
createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, features);
}
public BigInteger keyToBigInt(byte[] privateKey) {
return new BigInteger(1, privateKey);
}
public long randomNonce() {
return RANDOM.nextLong();
}
}

View File

@ -0,0 +1,205 @@
/*
* Copyright 2017 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.ports
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.UnixTime
import ch.dissem.bitmessage.utils.max
import org.slf4j.LoggerFactory
import java.math.BigInteger
import java.security.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
/**
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
*/
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography {
private val context by InternalContext
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray {
val mda = md("SHA-512")
mda.update(data, offset, length)
return mda.digest()
}
override fun sha512(vararg data: ByteArray): ByteArray {
return hash("SHA-512", *data)
}
override fun doubleSha512(vararg data: ByteArray): ByteArray {
val mda = md("SHA-512")
for (d in data) {
mda.update(d)
}
return mda.digest(mda.digest())
}
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
val mda = md("SHA-512")
mda.update(data, 0, length)
return mda.digest(mda.digest())
}
override fun ripemd160(vararg data: ByteArray): ByteArray {
return hash("RIPEMD160", *data)
}
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
val mda = md("SHA-256")
mda.update(data, 0, length)
return mda.digest(mda.digest())
}
override fun sha1(vararg data: ByteArray): ByteArray {
return hash("SHA-1", *data)
}
override fun randomBytes(length: Int): ByteArray {
val result = ByteArray(length)
RANDOM.nextBytes(result)
return result
}
override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
val initialHash = getInitialHash(`object`)
val target = getProofOfWorkTarget(`object`,
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
context.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
}
@Throws(InsufficientProofOfWorkException::class)
override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes)
val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`))
if (Bytes.lt(target, value, 8)) {
throw InsufficientProofOfWorkException(target, value)
}
}
@Throws(GeneralSecurityException::class)
protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray {
// TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network
val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider)
sig.initSign(privKey)
sig.update(data)
return sig.sign()
}
@Throws(GeneralSecurityException::class)
protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean {
for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) {
val sig = Signature.getInstance(algorithm, provider)
sig.initVerify(publicKey)
sig.update(data)
if (sig.verify(signature)) {
return true
}
}
return false
}
override fun getInitialHash(`object`: ObjectMessage): ByteArray {
return sha512(`object`.payloadBytesWithoutNonce)
}
override fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray {
@Suppress("NAME_SHADOWING")
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
@Suppress("NAME_SHADOWING")
val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes
val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now)
val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes)
val denominator = BigInteger.valueOf(nonceTrialsPerByte)
.multiply(
powLength.add(
powLength.multiply(TTL).divide(TWO_POW_16)
)
)
return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8)
}
private fun hash(algorithm: String, vararg data: ByteArray): ByteArray {
val mda = md(algorithm)
for (d in data) {
mda.update(d)
}
return mda.digest()
}
private fun md(algorithm: String): MessageDigest {
try {
return MessageDigest.getInstance(algorithm, provider)
} catch (e: GeneralSecurityException) {
throw ApplicationException(e)
}
}
override fun mac(key_m: ByteArray, data: ByteArray): ByteArray {
try {
val mac = Mac.getInstance("HmacSHA256", provider)
mac.init(SecretKeySpec(key_m, "HmacSHA256"))
return mac.doFinal(data)
} catch (e: GeneralSecurityException) {
throw ApplicationException(e)
}
}
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
return Factory.createPubkey(version, stream,
createPublicKey(privateSigningKey),
createPublicKey(privateEncryptionKey),
nonceTrialsPerByte, extraBytes, *features)
}
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
return BigInteger(1, privateKey)
}
override fun randomNonce(): Long {
return RANDOM.nextLong()
}
companion object {
protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)
private val RANDOM = SecureRandom()
private val TWO = BigInteger.valueOf(2)
private val TWO_POW_64 = TWO.pow(64)
private val TWO_POW_16 = TWO.pow(16)
}
}

View File

@ -1,162 +0,0 @@
/*
* Copyright 2016 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.ports;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
import ch.dissem.bitmessage.entity.valueobject.Label;
import ch.dissem.bitmessage.exception.ApplicationException;
import ch.dissem.bitmessage.utils.Strings;
import ch.dissem.bitmessage.utils.UnixTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static ch.dissem.bitmessage.utils.SqlStrings.join;
public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder {
protected InternalContext ctx;
@Override
public void setContext(InternalContext context) {
this.ctx = context;
}
/**
* @deprecated use {@link #saveContactIfNecessary(BitmessageAddress)} instead.
*/
@Deprecated
protected void safeSenderIfNecessary(Plaintext message) {
if (message.getId() == null) {
saveContactIfNecessary(message.getFrom());
}
}
protected void saveContactIfNecessary(BitmessageAddress contact) {
if (contact != null) {
BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(contact.getAddress());
if (savedAddress == null) {
ctx.getAddressRepository().save(contact);
} else if (savedAddress.getPubkey() == null && contact.getPubkey() != null) {
savedAddress.setPubkey(contact.getPubkey());
ctx.getAddressRepository().save(savedAddress);
}
if (savedAddress != null) {
contact.setAlias(savedAddress.getAlias());
}
}
}
@Override
public Plaintext getMessage(Object id) {
if (id instanceof Long) {
return single(find("id=" + id));
} else {
throw new IllegalArgumentException("Long expected for ID");
}
}
@Override
public Plaintext getMessage(InventoryVector iv) {
return single(find("iv=X'" + Strings.hex(iv.getHash()) + "'"));
}
@Override
public Plaintext getMessage(byte[] initialHash) {
return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"));
}
@Override
public Plaintext getMessageForAck(byte[] ackData) {
return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"));
}
@Override
public List<Plaintext> findMessages(Label label) {
if (label == null) {
return find("id NOT IN (SELECT message_id FROM Message_Label)");
} else {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")");
}
}
@Override
public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) {
return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'");
}
@Override
public List<Plaintext> findMessages(Plaintext.Status status) {
return find("status='" + status.name() + "'");
}
@Override
public List<Plaintext> findMessages(BitmessageAddress sender) {
return find("sender='" + sender.getAddress() + "'");
}
@Override
public List<Plaintext> findMessagesToResend() {
return find("status='" + Plaintext.Status.SENT.name() + "'" +
" AND next_try < " + UnixTime.now());
}
@Override
public List<Plaintext> findResponses(Plaintext parent) {
if (parent.getInventoryVector() == null) {
return Collections.emptyList();
}
return find("iv IN (SELECT child FROM Message_Parent"
+ " WHERE parent=X'" + Strings.hex(parent.getInventoryVector().getHash()) + "')");
}
@Override
public List<Plaintext> getConversation(UUID conversationId) {
return find("conversation=X'" + conversationId.toString().replace("-", "") + "'");
}
@Override
public List<Label> getLabels() {
return findLabels("1=1");
}
@Override
public List<Label> getLabels(Label.Type... types) {
return findLabels("type IN (" + join(types) + ")");
}
protected abstract List<Label> findLabels(String where);
protected <T> T single(Collection<T> collection) {
switch (collection.size()) {
case 0:
return null;
case 1:
return collection.iterator().next();
default:
throw new ApplicationException("This shouldn't happen, found " + collection.size() +
" items, one or none was expected");
}
}
protected abstract List<Plaintext> find(String where);
}

View File

@ -0,0 +1,126 @@
/*
* Copyright 2017 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.ports
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.valueobject.InventoryVector
import ch.dissem.bitmessage.entity.valueobject.Label
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.utils.SqlStrings.join
import ch.dissem.bitmessage.utils.Strings
import ch.dissem.bitmessage.utils.UnixTime
import java.util.*
abstract class AbstractMessageRepository : MessageRepository {
protected var ctx by InternalContext
protected fun saveContactIfNecessary(contact: BitmessageAddress?) {
contact?.let {
val savedAddress = ctx.addressRepository.getAddress(contact.address)
if (savedAddress == null) {
ctx.addressRepository.save(contact)
} else if (savedAddress.pubkey == null && contact.pubkey != null) {
savedAddress.pubkey = contact.pubkey
ctx.addressRepository.save(savedAddress)
}
if (savedAddress != null) {
contact.alias = savedAddress.alias
}
}
}
override fun getMessage(id: Any): Plaintext {
if (id is Long) {
return single(find("id=" + id)) ?: throw IllegalArgumentException("There is no message with id $id")
} else {
throw IllegalArgumentException("Long expected for ID")
}
}
override fun getMessage(iv: InventoryVector): Plaintext? {
return single(find("iv=X'" + Strings.hex(iv.hash) + "'"))
}
override fun getMessage(initialHash: ByteArray): Plaintext? {
return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'"))
}
override fun getMessageForAck(ackData: ByteArray): Plaintext? {
return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'"))
}
override fun findMessages(label: Label?): List<Plaintext> {
if (label == null) {
return find("id NOT IN (SELECT message_id FROM Message_Label)")
} else {
return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")")
}
}
override fun findMessages(status: Plaintext.Status, recipient: BitmessageAddress): List<Plaintext> {
return find("status='" + status.name + "' AND recipient='" + recipient.address + "'")
}
override fun findMessages(status: Plaintext.Status): List<Plaintext> {
return find("status='" + status.name + "'")
}
override fun findMessages(sender: BitmessageAddress): List<Plaintext> {
return find("sender='" + sender.address + "'")
}
override fun findMessagesToResend(): List<Plaintext> {
return find("status='" + Plaintext.Status.SENT.name + "'" +
" AND next_try < " + UnixTime.now)
}
override fun findResponses(parent: Plaintext): List<Plaintext> {
if (parent.inventoryVector == null) {
return emptyList()
}
return find("iv IN (SELECT child FROM Message_Parent"
+ " WHERE parent=X'" + Strings.hex(parent.inventoryVector!!.hash) + "')")
}
override fun getConversation(conversationId: UUID): List<Plaintext> {
return find("conversation=X'" + conversationId.toString().replace("-", "") + "'")
}
override fun getLabels(): List<Label> {
return findLabels("1=1")
}
override fun getLabels(vararg types: Label.Type): List<Label> {
return findLabels("type IN (" + join(*types) + ")")
}
protected abstract fun findLabels(where: String): List<Label>
protected fun <T> single(collection: Collection<T>): T? {
when (collection.size) {
0 -> return null
1 -> return collection.iterator().next()
else -> throw ApplicationException("This shouldn't happen, found " + collection.size +
" items, one or none was expected")
}
}
protected abstract fun find(where: String): List<Plaintext>
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,52 +14,51 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.ports;
package ch.dissem.bitmessage.ports
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.BitmessageAddress
import java.util.List;
public interface AddressRepository {
interface AddressRepository {
/**
* Returns a matching BitmessageAddress if there is one with the given ripe or tag, that
* has no public key yet. If it doesn't exist or already has a public key, null is returned.
*
* @param ripeOrTag Either ripe or tag (depending of address version) of an address with
* missing public key.
* * missing public key.
* *
* @return the matching address if there is one without public key, or null otherwise.
*/
BitmessageAddress findContact(byte[] ripeOrTag);
fun findContact(ripeOrTag: ByteArray): BitmessageAddress?
BitmessageAddress findIdentity(byte[] ripeOrTag);
fun findIdentity(ripeOrTag: ByteArray): BitmessageAddress?
/**
* @return all Bitmessage addresses that belong to this user, i.e. have a private key.
*/
List<BitmessageAddress> getIdentities();
fun getIdentities(): List<BitmessageAddress>
/**
* @return all subscribed chans.
*/
List<BitmessageAddress> getChans();
fun getChans(): List<BitmessageAddress>
List<BitmessageAddress> getSubscriptions();
fun getSubscriptions(): List<BitmessageAddress>
List<BitmessageAddress> getSubscriptions(long broadcastVersion);
fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress>
/**
* @return all Bitmessage addresses that have no private key or are chans.
*/
List<BitmessageAddress> getContacts();
fun getContacts(): List<BitmessageAddress>
/**
* Implementations must not delete cryptographic keys if they're not provided by <code>address</code>.
*
* Implementations must not delete cryptographic keys if they're not provided by `address`.
* @param address to save or update
*/
void save(BitmessageAddress address);
fun save(address: BitmessageAddress)
void remove(BitmessageAddress address);
fun remove(address: BitmessageAddress)
BitmessageAddress getAddress(String address);
fun getAddress(address: String): BitmessageAddress?
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,209 +14,250 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.ports;
package ch.dissem.bitmessage.ports
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.SecureRandom;
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
import ch.dissem.bitmessage.entity.ObjectMessage
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
import java.math.BigInteger
import java.security.MessageDigest
import java.security.SecureRandom
/**
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
* Provides some methods to help with hashing and encryption. All randoms are created using [SecureRandom],
* which should be secure enough.
*/
public interface Cryptography {
interface Cryptography {
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* *
* @param offset of the data to be hashed
* *
* @param length of the data to be hashed
* *
* @return SHA-512 hash of data within the given range
*/
byte[] sha512(byte[] data, int offset, int length);
fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray
/**
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
* A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* *
* @return SHA-512 hash of data
*/
byte[] sha512(byte[]... data);
fun sha512(vararg data: ByteArray): ByteArray
/**
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
* A helper method to calculate doubleSHA-512 hashes. Please note that a new [MessageDigest] object is created
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
* success on the same thread.
*
* @param data to get hashed
* *
* @return SHA-512 hash of data
*/
byte[] doubleSha512(byte[]... data);
fun doubleSha512(vararg data: ByteArray): ByteArray
/**
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
* </p>
*
*
* Please note that a new [MessageDigest] object is created at each call (to ensure thread safety), so you
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
*
* @param data to get hashed
* *
* @param length number of bytes to be taken into account
* *
* @return SHA-512 hash of data
*/
byte[] doubleSha512(byte[] data, int length);
fun doubleSha512(data: ByteArray, length: Int): ByteArray
/**
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
*
*
* Please note that a new [MessageDigest] object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* *
* @return RIPEMD-160 hash of data
*/
byte[] ripemd160(byte[]... data);
fun ripemd160(vararg data: ByteArray): ByteArray
/**
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
* to use for the hash calculation.
* <p>
* Please note that a new {@link MessageDigest} object is created at
*
*
* Please note that a new [MessageDigest] object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* *
* @param length number of bytes to be taken into account
* *
* @return SHA-256 hash of data
*/
byte[] doubleSha256(byte[] data, int length);
fun doubleSha256(data: ByteArray, length: Int): ByteArray
/**
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
* concatenation of all arrays, but might perform better.
* <p>
* Please note that a new {@link MessageDigest} object is created at
*
*
* Please note that a new [MessageDigest] object is created at
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
* order on the same thread.
* </p>
*
* @param data to get hashed
* *
* @return SHA hash of data
*/
byte[] sha1(byte[]... data);
fun sha1(vararg data: ByteArray): ByteArray
/**
* @param length number of bytes to return
* *
* @return an array of the given size containing random bytes
*/
byte[] randomBytes(int length);
fun randomBytes(length: Int): ByteArray
/**
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
* live.
*
* @param object to do the proof of work for
* *
* @param nonceTrialsPerByte difficulty
* *
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
* *
* @param callback to handle nonce once it's calculated
*/
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes, ProofOfWorkEngine.Callback callback);
fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long,
extraBytes: Long, callback: ProofOfWorkEngine.Callback)
/**
* @param object to be checked
* *
* @param nonceTrialsPerByte difficulty
* *
* @param extraBytes bytes to add to the object size
* *
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
*/
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
throws IOException;
@Throws(InsufficientProofOfWorkException::class)
fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long)
byte[] getInitialHash(ObjectMessage object);
fun getInitialHash(`object`: ObjectMessage): ByteArray
byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray
/**
* Calculates the MAC for a message (data)
*
* @param key_m the symmetric key used
* *
* @param data the message data to calculate the MAC for
* *
* @return the MAC
*/
byte[] mac(byte[] key_m, byte[] data);
fun mac(key_m: ByteArray, data: ByteArray): ByteArray
/**
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
* *
* @param data
* *
* @param key_e
* *
* @return
*/
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray
/**
* Create a new public key fom given private keys.
*
* @param version of the public key / address
* *
* @param stream of the address
* *
* @param privateSigningKey private key used for signing
* *
* @param privateEncryptionKey private key used for encryption
* *
* @param nonceTrialsPerByte proof of work difficulty
* *
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
* *
* @param features of the address
* *
* @return a public key object
*/
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey
/**
* @param privateKey private key as byte array
* *
* @return a public key corresponding to the given private key
*/
byte[] createPublicKey(byte[] privateKey);
fun createPublicKey(privateKey: ByteArray): ByteArray
/**
* @param privateKey private key as byte array
* *
* @return a big integer representation (unsigned) of the given bytes
*/
BigInteger keyToBigInt(byte[] privateKey);
fun keyToBigInt(privateKey: ByteArray): BigInteger
/**
* @param data to check
* *
* @param signature the signature of the message
* *
* @param pubkey the sender's public key
* *
* @return true if the signature is valid, false otherwise
*/
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean
/**
* Calculate the signature of data, using the given private key.
*
* @param data to be signed
* *
* @param privateKey to be used for signing
* *
* @return the signature
*/
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
fun getSignature(data: ByteArray, privateKey: ch.dissem.bitmessage.entity.valueobject.PrivateKey): ByteArray
/**
* @return a random number of type long
*/
long randomNonce();
fun randomNonce(): Long
byte[] multiply(byte[] k, byte[] r);
fun multiply(k: ByteArray, r: ByteArray): ByteArray
byte[] createPoint(byte[] x, byte[] y);
fun createPoint(x: ByteArray, y: ByteArray): ByteArray
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 Christian Basler
* Copyright 2017 Christian Basler
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,17 +14,14 @@
* limitations under the License.
*/
package ch.dissem.bitmessage.utils;
package ch.dissem.bitmessage.ports
import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography;
import org.junit.BeforeClass;
import ch.dissem.bitmessage.entity.CustomMessage
import ch.dissem.bitmessage.entity.MessagePayload
/**
* @author Christian Basler
*/
public class TestBase {
@BeforeClass
public static void setUpClass() {
Singleton.initialize(new BouncyCryptography());
}
interface CustomCommandHandler {
fun handle(request: CustomMessage): MessagePayload?
}

View File

@ -1,92 +0,0 @@
/*
* Copyright 2016 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.ports;
import ch.dissem.bitmessage.InternalContext;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
public class DefaultLabeler implements Labeler, InternalContext.ContextHolder {
private InternalContext ctx;
@Override
public void setLabels(Plaintext msg) {
msg.setStatus(RECEIVED);
if (msg.getType() == Plaintext.Type.BROADCAST) {
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD));
} else {
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD));
}
}
@Override
public void markAsDraft(Plaintext msg) {
msg.setStatus(DRAFT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT));
}
@Override
public void markAsSending(Plaintext msg) {
if (msg.getTo() != null && msg.getTo().getPubkey() == null) {
msg.setStatus(PUBKEY_REQUESTED);
} else {
msg.setStatus(DOING_PROOF_OF_WORK);
}
msg.removeLabel(Label.Type.DRAFT);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX));
}
@Override
public void markAsSent(Plaintext msg) {
msg.setStatus(SENT);
msg.removeLabel(Label.Type.OUTBOX);
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
}
@Override
public void markAsAcknowledged(Plaintext msg) {
msg.setStatus(SENT_ACKNOWLEDGED);
}
@Override
public void markAsRead(Plaintext msg) {
msg.removeLabel(Label.Type.UNREAD);
}
@Override
public void markAsUnread(Plaintext msg) {
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.UNREAD));
}
@Override
public void delete(Plaintext msg) {
msg.getLabels().clear();
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.TRASH));
}
@Override
public void archive(Plaintext msg) {
msg.getLabels().clear();
}
@Override
public void setContext(InternalContext ctx) {
this.ctx = ctx;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright 2017 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.ports
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.Plaintext
import ch.dissem.bitmessage.entity.Plaintext.Status.*
import ch.dissem.bitmessage.entity.valueobject.Label
open class DefaultLabeler : Labeler {
private var ctx by InternalContext
override fun setLabels(msg: Plaintext) {
msg.status = RECEIVED
if (msg.type === Plaintext.Type.BROADCAST) {
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD))
} else {
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD))
}
}
override fun markAsDraft(msg: Plaintext) {
msg.status = DRAFT
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.DRAFT))
}
override fun markAsSending(msg: Plaintext) {
if (msg.to != null && msg.to!!.pubkey == null) {
msg.status = PUBKEY_REQUESTED
} else {
msg.status = DOING_PROOF_OF_WORK
}
msg.removeLabel(Label.Type.DRAFT)
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.OUTBOX))
}
override fun markAsSent(msg: Plaintext) {
msg.status = SENT
msg.removeLabel(Label.Type.OUTBOX)
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.SENT))
}
override fun markAsAcknowledged(msg: Plaintext) {
msg.status = SENT_ACKNOWLEDGED
}
override fun markAsRead(msg: Plaintext) {
msg.removeLabel(Label.Type.UNREAD)
}
override fun markAsUnread(msg: Plaintext) {
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.UNREAD))
}
override fun delete(msg: Plaintext) {
msg.labels.clear()
msg.addLabels(ctx.messageRepository.getLabels(Label.Type.TRASH))
}
override fun archive(msg: Plaintext) {
msg.labels.clear()
}
}

Some files were not shown because too many files have changed in this diff Show More