Some changes needed for POW server and some general improvements

This commit is contained in:
Christian Basler 2015-12-21 15:13:48 +01:00
parent 61788802c5
commit fad3e07871
9 changed files with 143 additions and 37 deletions

View File

@ -66,6 +66,8 @@ public class BitmessageContext {
private final Listener listener; private final Listener listener;
private final NetworkHandler.MessageListener networkListener; private final NetworkHandler.MessageListener networkListener;
private final boolean sendPubkeyOnIdentityCreation;
private BitmessageContext(Builder builder) { private BitmessageContext(Builder builder) {
ctx = new InternalContext(builder); ctx = new InternalContext(builder);
listener = builder.listener; listener = builder.listener;
@ -75,6 +77,8 @@ public class BitmessageContext {
// one should be executed at any time. // one should be executed at any time.
pool = Executors.newFixedThreadPool(1); pool = Executors.newFixedThreadPool(1);
sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation;
new Timer().schedule(new TimerTask() { new Timer().schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
@ -100,12 +104,14 @@ public class BitmessageContext {
features features
)); ));
ctx.getAddressRepository().save(identity); ctx.getAddressRepository().save(identity);
pool.submit(new Runnable() { if (sendPubkeyOnIdentityCreation) {
@Override pool.submit(new Runnable() {
public void run() { @Override
ctx.sendPubkey(identity, identity.getStream()); public void run() {
} ctx.sendPubkey(identity, identity.getStream());
}); }
});
}
return identity; return identity;
} }
@ -325,6 +331,8 @@ public class BitmessageContext {
Listener listener; Listener listener;
int connectionLimit = 150; int connectionLimit = 150;
long connectionTTL = 12 * HOUR; long connectionTTL = 12 * HOUR;
boolean sendPubkeyOnIdentityCreation = true;
long pubkeyTTL = 28;
public Builder() { public Builder() {
} }
@ -399,6 +407,30 @@ public class BitmessageContext {
return this; 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.
* <p>
* 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.
* </p>
*/
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() { public BitmessageContext build() {
nonNull("inventory", inventory); nonNull("inventory", inventory);
nonNull("nodeRegistry", nodeRegistry); nonNull("nodeRegistry", nodeRegistry);

View File

@ -16,7 +16,9 @@
package ch.dissem.bitmessage; 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.Broadcast;
import ch.dissem.bitmessage.entity.payload.GetPubkey; import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.payload.ObjectPayload;
@ -29,8 +31,6 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.util.TreeSet; 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 * 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 * 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 clientNonce;
private final long networkNonceTrialsPerByte = 1000; private final long networkNonceTrialsPerByte = 1000;
private final long networkExtraBytes = 1000; private final long networkExtraBytes = 1000;
private final long pubkeyTTL;
private long connectionTTL; private long connectionTTL;
private int connectionLimit; private int connectionLimit;
@ -78,6 +79,7 @@ public class InternalContext {
this.port = builder.port; this.port = builder.port;
this.connectionLimit = builder.connectionLimit; this.connectionLimit = builder.connectionLimit;
this.connectionTTL = builder.connectionTTL; this.connectionTTL = builder.connectionTTL;
this.pubkeyTTL = builder.pubkeyTTL;
Singleton.initialize(security); Singleton.initialize(security);
@ -193,7 +195,7 @@ public class InternalContext {
public void sendPubkey(final BitmessageAddress identity, final long targetStream) { public void sendPubkey(final BitmessageAddress identity, final long targetStream) {
try { try {
long expires = UnixTime.now(+28 * DAY); long expires = UnixTime.now(pubkeyTTL);
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder() final ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream) .stream(targetStream)
@ -211,7 +213,7 @@ public class InternalContext {
} }
public void requestPubkey(final BitmessageAddress contact) { public void requestPubkey(final BitmessageAddress contact) {
long expires = UnixTime.now(+2 * DAY); long expires = UnixTime.now(+pubkeyTTL);
LOG.info("Expires at " + expires); LOG.info("Expires at " + expires);
final ObjectMessage response = new ObjectMessage.Builder() final ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream()) .stream(contact.getStream())

View File

@ -8,6 +8,10 @@ import ch.dissem.bitmessage.ports.MessageRepository;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine; import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import ch.dissem.bitmessage.ports.ProofOfWorkRepository; import ch.dissem.bitmessage.ports.ProofOfWorkRepository;
import ch.dissem.bitmessage.ports.Security; 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; import static ch.dissem.bitmessage.utils.Singleton.security;
@ -15,13 +19,19 @@ import static ch.dissem.bitmessage.utils.Singleton.security;
* @author Christian Basler * @author Christian Basler
*/ */
public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder { public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder {
private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class);
private Security security; private Security security;
private InternalContext ctx; private InternalContext ctx;
private ProofOfWorkRepository powRepo; private ProofOfWorkRepository powRepo;
private MessageRepository messageRepo; private MessageRepository messageRepo;
public void doMissingProofOfWork() { public void doMissingProofOfWork() {
for (byte[] initialHash : powRepo.getItems()) { List<byte[]> 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); ProofOfWorkRepository.Item item = powRepo.getItem(initialHash);
security.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this); 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) { public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) {
long nonceTrialsPerByte = recipient == null ? 0 : recipient.getPubkey().getNonceTrialsPerByte(); long nonceTrialsPerByte = recipient == null ?
long extraBytes = recipient == null ? 0 : recipient.getPubkey().getExtraBytes(); ctx.getNetworkNonceTrialsPerByte() : recipient.getPubkey().getNonceTrialsPerByte();
long extraBytes = recipient == null ?
ctx.getNetworkExtraBytes() : recipient.getPubkey().getExtraBytes();
powRepo.putObject(object, nonceTrialsPerByte, extraBytes); powRepo.putObject(object, nonceTrialsPerByte, extraBytes);
if (object.getPayload() instanceof PlaintextHolder) { if (object.getPayload() instanceof PlaintextHolder) {

View File

@ -43,7 +43,7 @@ public class CustomMessage implements MessagePayload {
this.data = data; 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(); AccessCounter counter = new AccessCounter();
return new CustomMessage(varString(in, counter), bytes(in, length - counter.length())); return new CustomMessage(varString(in, counter), bytes(in, length - counter.length()));
} }

View File

@ -43,6 +43,8 @@ public abstract class AbstractSecurity implements Security, InternalContext.Cont
public static final Logger LOG = LoggerFactory.getLogger(Security.class); public static final Logger LOG = LoggerFactory.getLogger(Security.class);
private static final SecureRandom RANDOM = new SecureRandom(); private static final SecureRandom RANDOM = new SecureRandom();
private static final BigInteger TWO = BigInteger.valueOf(2); 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 final String provider;
private InternalContext context; private InternalContext context;
@ -96,18 +98,14 @@ public abstract class AbstractSecurity implements Security, InternalContext.Cont
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
long extraBytes, ProofOfWorkEngine.Callback callback) { long extraBytes, ProofOfWorkEngine.Callback callback) {
try { nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte());
nonceTrialsPerByte = max(nonceTrialsPerByte, context.getNetworkNonceTrialsPerByte()); extraBytes = max(extraBytes, context.getNetworkExtraBytes());
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); context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) 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()); 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 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 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); return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
} }

View File

@ -136,6 +136,8 @@ public interface Security {
byte[] getInitialHash(ObjectMessage object); byte[] getInitialHash(ObjectMessage object);
byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes);
/** /**
* Calculates the MAC for a message (data) * Calculates the MAC for a message (data)
* *

View File

@ -54,8 +54,8 @@ public class CryptoCustomMessage<T extends Streamable> extends CustomMessage {
this.dataReader = dataReader; this.dataReader = dataReader;
} }
public static <T extends Streamable> CryptoCustomMessage<T> read(byte[] data, Reader<T> dataReader) throws IOException { public static <T extends Streamable> CryptoCustomMessage<T> read(CustomMessage data, Reader<T> dataReader) throws IOException {
CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data), data.length); CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data.getData()), data.getData().length);
return new CryptoCustomMessage<>(cryptoBox, dataReader); return new CryptoCustomMessage<>(cryptoBox, dataReader);
} }
@ -111,6 +111,7 @@ public class CryptoCustomMessage<T extends Streamable> extends CustomMessage {
@Override @Override
public void write(OutputStream out) throws IOException { public void write(OutputStream out) throws IOException {
Encode.varString(COMMAND, out);
container.write(out); container.write(out);
} }

View File

@ -24,6 +24,7 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Arrays;
import static ch.dissem.bitmessage.utils.Decode.*; import static ch.dissem.bitmessage.utils.Decode.*;
@ -80,6 +81,28 @@ public class ProofOfWorkRequest implements Streamable {
Encode.varBytes(data, out); 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<ProofOfWorkRequest> { public static class Reader implements CryptoCustomMessage.Reader<ProofOfWorkRequest> {
private final BitmessageAddress identity; private final BitmessageAddress identity;
@ -93,7 +116,6 @@ public class ProofOfWorkRequest implements Streamable {
} }
} }
public enum Request { public enum Request {
CALCULATE, CALCULATE,
CALCULATING, CALCULATING,

View File

@ -17,8 +17,10 @@
package ch.dissem.bitmessage.extensions; package ch.dissem.bitmessage.extensions;
import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.CustomMessage;
import ch.dissem.bitmessage.entity.payload.GenericPayload; import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey; 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.TestBase;
import ch.dissem.bitmessage.utils.TestUtils; import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test; import org.junit.Test;
@ -33,7 +35,7 @@ import static org.junit.Assert.assertEquals;
public class CryptoCustomMessageTest extends TestBase { public class CryptoCustomMessageTest extends TestBase {
@Test @Test
public void testEncryptThenDecrypt() throws Exception { public void ensureEncryptThenDecryptYieldsSameObject() throws Exception {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey);
@ -45,14 +47,40 @@ public class CryptoCustomMessageTest extends TestBase {
messageBefore.write(out); messageBefore.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
CryptoCustomMessage<GenericPayload> messageAfter = CryptoCustomMessage.read(out.toByteArray(), new CryptoCustomMessage.Reader<GenericPayload>() { CustomMessage customMessage = CustomMessage.read(in, out.size());
@Override CryptoCustomMessage<GenericPayload> messageAfter = CryptoCustomMessage.read(customMessage,
public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException { new CryptoCustomMessage.Reader<GenericPayload>() {
return GenericPayload.read(0, in, 1, 100); @Override
} public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException {
}); return GenericPayload.read(0, in, 1, 100);
}
});
GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
assertEquals(payloadBefore, payloadAfter); 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<ProofOfWorkRequest> 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<ProofOfWorkRequest> messageAfter = CryptoCustomMessage.read(customMessage,
new ProofOfWorkRequest.Reader(sendingIdentity));
ProofOfWorkRequest requestAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey());
assertEquals(requestBefore, requestAfter);
}
} }