diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
index 511aaea..1c4295e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java
@@ -66,6 +66,8 @@ public class BitmessageContext {
private final Listener listener;
private final NetworkHandler.MessageListener networkListener;
+ private final boolean sendPubkeyOnIdentityCreation;
+
private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder);
listener = builder.listener;
@@ -75,6 +77,8 @@ public class BitmessageContext {
// one should be executed at any time.
pool = Executors.newFixedThreadPool(1);
+ sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
+
new Timer().schedule(new TimerTask() {
@Override
public void run() {
@@ -100,12 +104,14 @@ public class BitmessageContext {
features
));
ctx.getAddressRepository().save(identity);
- pool.submit(new Runnable() {
- @Override
- public void run() {
- ctx.sendPubkey(identity, identity.getStream());
- }
- });
+ if (sendPubkeyOnIdentityCreation) {
+ pool.submit(new Runnable() {
+ @Override
+ public void run() {
+ ctx.sendPubkey(identity, identity.getStream());
+ }
+ });
+ }
return identity;
}
@@ -325,6 +331,8 @@ public class BitmessageContext {
Listener listener;
int connectionLimit = 150;
long connectionTTL = 12 * HOUR;
+ boolean sendPubkeyOnIdentityCreation = true;
+ long pubkeyTTL = 28;
public Builder() {
}
@@ -399,6 +407,30 @@ public class BitmessageContext {
return this;
}
+ /**
+ * By default a client will send the public key when an identity is being created. On weaker devices
+ * this behaviour might not be desirable.
+ */
+ public Builder doNotSendPubkeyOnIdentityCreation() {
+ this.sendPubkeyOnIdentityCreation = false;
+ return this;
+ }
+
+ /**
+ * Time to live in seconds for public keys the client sends. Defaults to the maximum of 28 days,
+ * but on weak devices smaller values might be desirable.
+ *
+ * Please be aware that this might cause some problems where you can't receive a message (the
+ * sender can't receive your public key) in some special situations. Also note that it's probably
+ * not a good idea to set it too low.
+ *
+ */
+ public Builder pubkeyTTL(long days) {
+ if (days < 0 || days > 28 * DAY) throw new IllegalArgumentException("TTL must be between 1 and 28 days");
+ this.pubkeyTTL = days;
+ return this;
+ }
+
public BitmessageContext build() {
nonNull("inventory", inventory);
nonNull("nodeRegistry", nodeRegistry);
diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
index 89f3082..1fe8007 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java
@@ -16,7 +16,9 @@
package ch.dissem.bitmessage;
-import ch.dissem.bitmessage.entity.*;
+import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.Encrypted;
+import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Broadcast;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
@@ -29,8 +31,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
-import static ch.dissem.bitmessage.utils.UnixTime.DAY;
-
/**
* 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
@@ -59,6 +59,7 @@ public class InternalContext {
private final long clientNonce;
private final long networkNonceTrialsPerByte = 1000;
private final long networkExtraBytes = 1000;
+ private final long pubkeyTTL;
private long connectionTTL;
private int connectionLimit;
@@ -78,6 +79,7 @@ public class InternalContext {
this.port = builder.port;
this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL;
+ this.pubkeyTTL = builder.pubkeyTTL;
Singleton.initialize(security);
@@ -193,7 +195,7 @@ public class InternalContext {
public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
try {
- long expires = UnixTime.now(+28 * DAY);
+ long expires = UnixTime.now(pubkeyTTL);
LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream)
@@ -211,7 +213,7 @@ public class InternalContext {
}
public void requestPubkey(final BitmessageAddress contact) {
- long expires = UnixTime.now(+2 * DAY);
+ long expires = UnixTime.now(+pubkeyTTL);
LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream())
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
index da59105..82c384c 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java
@@ -8,6 +8,10 @@ import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.ports.Security;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
import static ch.dissem.bitmessage.utils.Singleton.security;
@@ -15,13 +19,19 @@ import static ch.dissem.bitmessage.utils.Singleton.security;
* @author Christian Basler
*/
public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
+ private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
+
private Security security;
private InternalContext ctx;
private ProofOfWorkRepository powRepo;
private MessageRepository messageRepo;
public void doMissingProofOfWork() {
- for (byte[] initialHash : powRepo.getItems()) {
+ List items = powRepo.getItems();
+ if (items.isEmpty()) return;
+
+ LOG.info("Doing POW for " + items.size() + " tasks.");
+ for (byte[] initialHash : items) {
ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
security.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this);
}
@@ -32,8 +42,10 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC
}
public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
- long nonceTrialsPerByte = recipient == null ? 0 : recipient.getPubkey().getNonceTrialsPerByte();
- long extraBytes = recipient == null ? 0 : recipient.getPubkey().getExtraBytes();
+ long nonceTrialsPerByte = recipient == null ?
+ ctx.getNetworkNonceTrialsPerByte() : recipient.getPubkey().getNonceTrialsPerByte();
+ long extraBytes = recipient == null ?
+ ctx.getNetworkExtraBytes() : recipient.getPubkey().getExtraBytes();
powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
if (object.getPayload() instanceof PlaintextHolder) {
diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
index 126b808..5702b6e 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java
@@ -43,7 +43,7 @@ public class CustomMessage implements MessagePayload {
this.data = data;
}
- public static MessagePayload read(InputStream in, int length) throws IOException {
+ 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()));
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
index 0dea04c..053a776 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java
@@ -43,6 +43,8 @@ public abstract class AbstractSecurity implements Security, InternalContext.Cont
public static final Logger LOG = LoggerFactory.getLogger(Security.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);
private final String provider;
private InternalContext context;
@@ -96,18 +98,14 @@ public abstract class AbstractSecurity implements Security, InternalContext.Cont
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes, ProofOfWorkEngine.Callback callback) {
- try {
- nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
- extraBytes = max(extraBytes, context.getNetworkExtraBytes());
+ nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
+ extraBytes = max(extraBytes, context.getNetworkExtraBytes());
- byte[] initialHash = getInitialHash(object);
+ byte[] initialHash = getInitialHash(object);
- byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
+ byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
- context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
}
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
@@ -124,11 +122,20 @@ public abstract class AbstractSecurity implements Security, InternalContext.Cont
return sha512(object.getPayloadBytesWithoutNonce());
}
- private byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
+ @Override
+ public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) {
+ if (nonceTrialsPerByte == 0) nonceTrialsPerByte = context.getNetworkNonceTrialsPerByte();
+ if (extraBytes == 0) extraBytes = context.getNetworkExtraBytes();
+
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
- BigInteger numerator = TWO.pow(64);
+ BigInteger numerator = TWO_POW_64;
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
- BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
+ BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte)
+ .multiply(
+ powLength.add(
+ powLength.multiply(TTL).divide(TWO_POW_16)
+ )
+ );
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
}
diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
index 8fc7e20..e76b21f 100644
--- a/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
+++ b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
@@ -136,6 +136,8 @@ public interface Security {
byte[] getInitialHash(ObjectMessage object);
+ byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
+
/**
* Calculates the MAC for a message (data)
*
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
index 9a9e2dc..49c6f1b 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java
@@ -54,8 +54,8 @@ public class CryptoCustomMessage extends CustomMessage {
this.dataReader = dataReader;
}
- public static CryptoCustomMessage read(byte[] data, Reader dataReader) throws IOException {
- CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data), data.length);
+ public static CryptoCustomMessage read(CustomMessage data, Reader dataReader) throws IOException {
+ CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data.getData()), data.getData().length);
return new CryptoCustomMessage<>(cryptoBox, dataReader);
}
@@ -111,6 +111,7 @@ public class CryptoCustomMessage extends CustomMessage {
@Override
public void write(OutputStream out) throws IOException {
+ Encode.varString(COMMAND, out);
container.write(out);
}
diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
index 196005d..0024aaa 100644
--- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
+++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java
@@ -24,6 +24,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.Arrays;
import static ch.dissem.bitmessage.utils.Decode.*;
@@ -80,6 +81,28 @@ public class ProofOfWorkRequest implements Streamable {
Encode.varBytes(data, out);
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ProofOfWorkRequest other = (ProofOfWorkRequest) o;
+
+ if (!sender.equals(other.sender)) return false;
+ if (!Arrays.equals(initialHash, other.initialHash)) return false;
+ if (request != other.request) return false;
+ return Arrays.equals(data, other.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = sender.hashCode();
+ result = 31 * result + Arrays.hashCode(initialHash);
+ result = 31 * result + request.hashCode();
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
+
public static class Reader implements CryptoCustomMessage.Reader {
private final BitmessageAddress identity;
@@ -93,7 +116,6 @@ public class ProofOfWorkRequest implements Streamable {
}
}
-
public enum Request {
CALCULATE,
CALCULATING,
diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
index 98e97a1..c1303e3 100644
--- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
+++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java
@@ -17,8 +17,10 @@
package ch.dissem.bitmessage.extensions;
import ch.dissem.bitmessage.entity.BitmessageAddress;
+import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
+import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest;
import ch.dissem.bitmessage.utils.TestBase;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
@@ -33,7 +35,7 @@ import static org.junit.Assert.assertEquals;
public class CryptoCustomMessageTest extends TestBase {
@Test
- public void testEncryptThenDecrypt() throws Exception {
+ public void ensureEncryptThenDecryptYieldsSameObject() throws Exception {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
@@ -45,14 +47,40 @@ public class CryptoCustomMessageTest extends TestBase {
messageBefore.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- CryptoCustomMessage messageAfter = CryptoCustomMessage.read(out.toByteArray(), new CryptoCustomMessage.Reader() {
- @Override
- public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
- return GenericPayload.read(0, in, 1, 100);
- }
- });
+ CustomMessage customMessage = CustomMessage.read(in, out.size());
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(customMessage,
+ new CryptoCustomMessage.Reader() {
+ @Override
+ public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
+ return GenericPayload.read(0, in, 1, 100);
+ }
+ });
GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
assertEquals(payloadBefore, payloadAfter);
}
+
+ @Test
+ public void testWithActualRequest() throws Exception {
+ PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
+ final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
+
+ ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, security().randomBytes(64),
+ ProofOfWorkRequest.Request.CALCULATE);
+
+ CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(requestBefore);
+ messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey()));
+
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ messageBefore.write(out);
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+
+ CustomMessage customMessage = CustomMessage.read(in, out.size());
+ CryptoCustomMessage messageAfter = CryptoCustomMessage.read(customMessage,
+ new ProofOfWorkRequest.Reader(sendingIdentity));
+ ProofOfWorkRequest requestAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
+
+ assertEquals(requestBefore, requestAfter);
+ }
}