diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index b19c303..d46c36c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -18,12 +18,9 @@ package ch.dissem.bitmessage; import ch.dissem.bitmessage.entity.*; import ch.dissem.bitmessage.entity.payload.Broadcast; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.entity.payload.ObjectType; import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.DecryptionFailedException; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.*; @@ -178,23 +175,16 @@ public class BitmessageContext { if (to == null || to.getPubkey() != null) { LOG.info("Sending message."); ctx.getMessageRepository().save(msg); - ctx.send( - msg.getFrom(), - to, - wrapInObjectPayload(msg), - TTL.msg() - ); - } - } - - private ObjectPayload wrapInObjectPayload(Plaintext msg) { - switch (msg.getType()) { - case MSG: - return new Msg(msg); - case BROADCAST: - return Factory.getBroadcast(msg); - default: - throw new ApplicationException("Unknown message type " + msg.getType()); + if (msg.getType() == MSG) { + ctx.send(msg, TTL.msg()); + } else { + ctx.send( + msg.getFrom(), + to, + Factory.getBroadcast(msg), + TTL.msg() + ); + } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java index ce7ab8d..14c405c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java @@ -111,12 +111,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { for (Plaintext msg : messages) { ctx.getLabeler().markAsSending(msg); ctx.getMessageRepository().save(msg); - ctx.send( - msg.getFrom(), - msg.getTo(), - new Msg(msg), - +2 * DAY - ); + ctx.send(msg, +2 * DAY); } } diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java index 58333d5..884dd16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -160,6 +160,15 @@ public class InternalContext { return port; } + public void send(final Plaintext plaintext, final long timeToLive) { + if (plaintext.getAckMessage() != null) { + long expires = UnixTime.now(+timeToLive); + proofOfWorkService.doProofOfWorkWithAck(plaintext, expires); + } else { + send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), timeToLive); + } + } + public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, final long timeToLive) { try { @@ -174,26 +183,12 @@ public class InternalContext { if (object.isSigned()) { object.sign(from.getPrivateKey()); } - if (payload instanceof Msg && recipient.has(Pubkey.Feature.DOES_ACK)) { - final ObjectMessage ackMessage = ((Msg) payload).getPlaintext().getAckMessage(); - cryptography.doProofOfWork(ackMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - // FIXME: the message gets lost if calculation is cancelled - // (e.g. by terminating the application) - ackMessage.setNonce(nonce); - object.encrypt(recipient.getPubkey()); - proofOfWorkService.doProofOfWork(recipient, object); - } - }); - } else { - if (payload instanceof Broadcast) { - ((Broadcast) payload).encrypt(); - } else if (payload instanceof Encrypted) { - object.encrypt(recipient.getPubkey()); - } - proofOfWorkService.doProofOfWork(to, object); + 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); } diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java index 98fc9a1..6df0cc0 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java @@ -1,14 +1,15 @@ 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.PlaintextHolder; +import ch.dissem.bitmessage.entity.*; +import ch.dissem.bitmessage.entity.payload.Broadcast; +import ch.dissem.bitmessage.entity.payload.Msg; +import ch.dissem.bitmessage.entity.payload.ObjectPayload; 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; @@ -16,7 +17,7 @@ import java.util.List; 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.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * @author Christian Basler @@ -35,7 +36,7 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC LOG.info("Doing POW for " + items.size() + " tasks."); for (byte[] initialHash : items) { - ProofOfWorkRepository.Item item = powRepo.getItem(initialHash); + Item item = powRepo.getItem(initialHash); cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, this); } } @@ -59,25 +60,50 @@ public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalC cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this); } + public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) { + final ObjectMessage ack = plaintext.getAckMessage(); + 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) { - ObjectMessage object = powRepo.getItem(initialHash).object; - object.setNonce(nonce); - Plaintext plaintext = messageRepo.getMessage(initialHash); - if (plaintext != null) { - plaintext.setInventoryVector(object.getInventoryVector()); - ctx.getLabeler().markAsSent(plaintext); - messageRepo.save(plaintext); + Item item = powRepo.getItem(initialHash); + if (item.message == null) { + ObjectMessage object = powRepo.getItem(initialHash).object; + object.setNonce(nonce); + Plaintext plaintext = messageRepo.getMessage(initialHash); + if (plaintext != null) { + plaintext.setInventoryVector(object.getInventoryVector()); + ctx.getLabeler().markAsSent(plaintext); + messageRepo.save(plaintext); + } + ctx.getInventory().storeObject(object); + powRepo.removeObject(initialHash); + 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); } - ctx.getInventory().storeObject(object); - powRepo.removeObject(initialHash); - ctx.getNetworkHandler().offer(object.getInventoryVector()); } @Override public void setContext(InternalContext ctx) { this.ctx = ctx; - this.cryptography = security(); + this.cryptography = cryptography(); this.powRepo = ctx.getProofOfWorkRepository(); this.messageRepo = ctx.getMessageRepository(); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index 84b349d..ce199c4 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -37,7 +37,7 @@ 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.security; +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 @@ -74,19 +74,19 @@ public class BitmessageAddress implements Serializable { Encode.varInt(version, os); Encode.varInt(stream, os); if (version < 4) { - byte[] checksum = security().sha512(os.toByteArray(), ripe); + 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 = security().doubleSha512(os.toByteArray(), ripe); + 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 = security().doubleSha512(os.toByteArray()); + byte[] checksum = cryptography().doubleSha512(os.toByteArray()); os.write(checksum, 0, 4); this.address = "BM-" + Base58.encode(os.toByteArray()); } catch (IOException e) { @@ -147,18 +147,18 @@ public class BitmessageAddress implements Serializable { this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); // test checksum - byte[] checksum = security().doubleSha512(bytes, bytes.length - 4); + 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 = security().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); + checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); this.tag = null; this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); } else { - checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); + checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); this.tag = Arrays.copyOfRange(checksum, 32, 64); this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); } @@ -173,7 +173,7 @@ public class BitmessageAddress implements Serializable { Encode.varInt(version, out); Encode.varInt(stream, out); out.write(ripe); - return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64); + return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64); } catch (IOException e) { throw new ApplicationException(e); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java index 347af6c..860c6ed 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java @@ -27,7 +27,7 @@ import java.security.GeneralSecurityException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * A network message is exchanged between two nodes. @@ -51,7 +51,7 @@ public class NetworkMessage implements Streamable { * First 4 bytes of sha512(payload) */ private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException { - byte[] d = security().sha512(bytes); + byte[] d = cryptography().sha512(bytes); return new byte[]{d[0], d[1], d[2], d[3]}; } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java index a0878c2..6f74257 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -32,7 +32,7 @@ import java.io.OutputStream; import java.util.Arrays; import java.util.Objects; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * The 'object' command sends an object that is shared throughout the network. @@ -96,7 +96,7 @@ public class ObjectMessage implements MessagePayload { public InventoryVector getInventoryVector() { return new InventoryVector( - Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32) + Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32) ); } @@ -121,7 +121,7 @@ public class ObjectMessage implements MessagePayload { public void sign(PrivateKey key) { if (payload.isSigned()) { - payload.setSignature(security().getSignature(getBytesToSign(), key)); + payload.setSignature(cryptography().getSignature(getBytesToSign(), key)); } } @@ -155,7 +155,7 @@ public class ObjectMessage implements MessagePayload { public boolean isSignatureValid(Pubkey pubkey) throws IOException { if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first"); - return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey); + return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey); } @Override diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java index c0a33ef..a2b66f7 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -28,7 +28,7 @@ import ch.dissem.bitmessage.utils.UnixTime; import java.io.*; import java.util.*; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * The unencrypted message to be sent by 'msg' or 'broadcast'. @@ -511,7 +511,7 @@ public class Plaintext implements Streamable { to = new BitmessageAddress(0, 0, destinationRipe); } if (type == Type.MSG && ackMessage == null && ackData == null) { - ackData = security().randomBytes(32); + ackData = cryptography().randomBytes(32); } return new Plaintext(this); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index 1533a2d..a583330 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -26,7 +26,7 @@ import java.io.IOException; import java.util.Objects; import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * Users who are subscribed to the sending address will see the message appear in their inbox. @@ -81,7 +81,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai } public void encrypt() throws IOException { - encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey())); + encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey())); } @Override diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java index 6609070..89d7bd7 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java @@ -27,7 +27,7 @@ import java.io.*; import java.util.Arrays; import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; public class CryptoBox implements Streamable { @@ -50,22 +50,22 @@ public class CryptoBox implements Streamable { // 1. The destination public key is called K. // 2. Generate 16 random bytes using a secure random number generator. Call them IV. - initializationVector = security().randomBytes(16); + initializationVector = cryptography().randomBytes(16); // 3. Generate a new random EC key pair with private key called r and public key called R. - byte[] r = security().randomBytes(PRIVATE_KEY_SIZE); - R = security().createPublicKey(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 = security().multiply(K, r); + 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 = security().sha512(X); + 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 = security().crypt(true, data, key_e, initializationVector); + 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); @@ -75,7 +75,7 @@ public class CryptoBox implements Streamable { private CryptoBox(Builder builder) { initializationVector = builder.initializationVector; curveType = builder.curveType; - R = security().createPoint(builder.xComponent, builder.yComponent); + R = cryptography().createPoint(builder.xComponent, builder.yComponent); encrypted = builder.encrypted; mac = builder.mac; } @@ -101,9 +101,9 @@ public class CryptoBox implements Streamable { 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 = security().multiply(R, k); + byte[] P = cryptography().multiply(R, k); // 3. Use the X component of public key P and calculate the SHA512 hash H. - byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33)); + 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); @@ -116,14 +116,14 @@ public class CryptoBox implements Streamable { // 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(security().crypt(false, encrypted, key_e, initializationVector)); + return new ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)); } private byte[] calculateMac(byte[] key_m) { try { ByteArrayOutputStream macData = new ByteArrayOutputStream(); writeWithoutMAC(macData); - return security().mac(key_m, macData.toByteArray()); + return cryptography().mac(key_m, macData.toByteArray()); } catch (IOException e) { throw new ApplicationException(e); } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java index 789c64c..c476bf9 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java @@ -20,7 +20,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * Public keys for signing and encryption, the answer to a 'getpubkey' request. @@ -35,7 +35,7 @@ public abstract class Pubkey extends ObjectPayload { } public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { - return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey)); + return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)); } public abstract byte[] getSigningKey(); @@ -45,7 +45,7 @@ public abstract class Pubkey extends ObjectPayload { public abstract int getBehaviorBitfield(); public byte[] getRipe() { - return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey())); + return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey())); } public long getNonceTrialsPerByte() { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java index b9c3afb..716afc6 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java @@ -30,7 +30,7 @@ import java.io.*; import java.util.ArrayList; import java.util.List; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying @@ -53,15 +53,15 @@ public class PrivateKey implements Streamable { byte[] pubEK; byte[] ripe; do { - privSK = security().randomBytes(PRIVATE_KEY_SIZE); - privEK = security().randomBytes(PRIVATE_KEY_SIZE); - pubSK = security().createPublicKey(privSK); - pubEK = security().createPublicKey(privEK); + 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 = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, + this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, nonceTrialsPerByte, extraBytes, features); } @@ -118,11 +118,11 @@ public class PrivateKey implements Streamable { long encryptionKeyNonce = nextNonce + 1; byte[] ripe; do { - privEK = Bytes.truncate(security().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32); - privSK = Bytes.truncate(security().sha512(seed, Encode.varInt(signingKeyNonce)), 32); - pubSK = security().createPublicKey(privSK); - pubEK = security().createPublicKey(privEK); - ripe = security().ripemd160(security().sha512(pubSK, pubEK)); + 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; diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java index cfa3229..49db9d6 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -32,7 +32,7 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * Creates {@link NetworkMessage} objects from {@link InputStream InputStreams} @@ -117,8 +117,8 @@ public class Factory { BitmessageAddress temp = new BitmessageAddress(address); PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey, createPubkey(temp.getVersion(), temp.getStream(), - security().createPublicKey(privateSigningKey), - security().createPublicKey(privateEncryptionKey), + cryptography().createPublicKey(privateSigningKey), + cryptography().createPublicKey(privateEncryptionKey), nonceTrialsPerByte, extraBytes, behaviourBitfield)); BitmessageAddress result = new BitmessageAddress(privateKey); if (!result.getAddress().equals(address)) { diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java index d13e73e..af9839f 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java @@ -32,7 +32,7 @@ import java.io.IOException; import java.io.InputStream; import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * Creates protocol v3 network messages from {@link InputStream InputStreams} @@ -183,7 +183,7 @@ class V3MessageFactory { } private static boolean testChecksum(byte[] checksum, byte[] payload) { - byte[] payloadChecksum = security().sha512(payload); + byte[] payloadChecksum = cryptography().sha512(payload); for (int i = 0; i < checksum.length; i++) { if (checksum[i] != payloadChecksum[i]) { return false; diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java index 9e949a7..c4cff5c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java @@ -30,6 +30,8 @@ public interface MessageRepository { int countUnread(Label label); + Plaintext getMessage(Object id); + Plaintext getMessage(byte[] initialHash); List findMessages(Label label); diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java index 739c172..61dc2ab 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java @@ -1,6 +1,8 @@ package ch.dissem.bitmessage.ports; +import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; import java.util.List; @@ -16,6 +18,8 @@ public interface ProofOfWorkRepository { void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); + void putObject(Item item); + void removeObject(byte[] initialHash); class Item { @@ -23,10 +27,20 @@ public interface ProofOfWorkRepository { public final long nonceTrialsPerByte; public final long extraBytes; + // Needed for ACK POW calculation + public final Long expirationTime; + public final Plaintext message; + public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { + this(object, nonceTrialsPerByte, extraBytes, 0, null); + } + + public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) { this.object = object; this.nonceTrialsPerByte = nonceTrialsPerByte; this.extraBytes = extraBytes; + this.expirationTime = expirationTime; + this.message = message; } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java index a751c65..2eeaa97 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java @@ -30,7 +30,7 @@ public class Singleton { } } - public static Cryptography security() { + public static Cryptography cryptography() { return cryptography; } } diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java index 91dd94e..6e49853 100644 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java @@ -24,19 +24,20 @@ 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.ports.*; -import ch.dissem.bitmessage.utils.*; +import ch.dissem.bitmessage.utils.MessageMatchers; +import ch.dissem.bitmessage.utils.Singleton; +import ch.dissem.bitmessage.utils.TTL; +import ch.dissem.bitmessage.utils.TestUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.junit.Before; import org.junit.Test; import java.util.*; -import java.util.Collections; -import java.util.stream.Collectors; import static ch.dissem.bitmessage.entity.payload.ObjectType.*; import static ch.dissem.bitmessage.utils.MessageMatchers.object; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -79,9 +80,14 @@ public class BitmessageContextTest { return result; } + @Override + public void putObject(Item item) { + items.put(new InventoryVector(cryptography().getInitialHash(item.object)), item); + } + @Override public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - items.put(new InventoryVector(security().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes)); + items.put(new InventoryVector(cryptography().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes)); } @Override @@ -196,7 +202,7 @@ public class BitmessageContextTest { public void ensureMessageIsSent() throws Exception { ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), "Subject", "Message"); - assertEquals(1, ctx.internals().getProofOfWorkRepository().getItems().size()); + assertEquals(2, ctx.internals().getProofOfWorkRepository().getItems().size()); verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce()) .putObject(object(MSG), eq(1000L), eq(1000L)); verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Plaintext.Type.MSG)); diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java index eacaab2..170cb93 100644 --- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java @@ -99,7 +99,7 @@ public class DefaultMessageListenerTest extends TestBase { .payload(identity.getPubkey()) .build(); objectMessage.sign(identity.getPrivateKey()); - objectMessage.encrypt(Singleton.security().createPublicKey(identity.getPublicDecryptionKey())); + objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.getPublicDecryptionKey())); listener.receive(objectMessage); verify(addressRepo).save(any(BitmessageAddress.class)); diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java index be57e4a..0a6ee25 100644 --- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java @@ -30,14 +30,14 @@ import org.junit.Test; import java.io.IOException; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; public class EncryptionTest extends TestBase { @Test public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException { - GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100)); + GenericPayload before = new GenericPayload(0, 1, cryptography().randomBytes(100)); PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java index 5d10cdf..fced84f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java @@ -28,7 +28,7 @@ import java.util.Arrays; import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.junit.Assert.*; public class BitmessageAddressTest extends TestBase { @@ -126,7 +126,7 @@ public class BitmessageAddressTest extends TestBase { System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n"); BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); + cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); assertEquals(address_string, address.getAddress()); } @@ -136,7 +136,7 @@ public class BitmessageAddressTest extends TestBase { byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU"); byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz"); BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))); + cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))); assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress()); } @@ -151,7 +151,7 @@ public class BitmessageAddressTest extends TestBase { if (bytes.length != 37) throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); - byte[] hash = security().doubleSha256(bytes, 33); + byte[] hash = cryptography().doubleSha256(bytes, 33); for (int i = 0; i < 4; i++) { if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); } diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java index aa08475..26d8a35 100644 --- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java @@ -30,7 +30,7 @@ import java.util.ArrayList; import java.util.Collections; import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.junit.Assert.*; public class SerializationTest extends TestBase { @@ -102,7 +102,7 @@ public class SerializationTest extends TestBase { public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception { ArrayList<InventoryVector> ivs = new ArrayList<>(50000); for (int i = 0; i < 50000; i++) { - ivs.add(new InventoryVector(security().randomBytes(32))); + ivs.add(new InventoryVector(cryptography().randomBytes(32))); } Inv inv = new Inv.Builder().inventory(ivs).build(); diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java index 1ed4aac..c2efb1f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java @@ -21,7 +21,7 @@ import ch.dissem.bitmessage.utils.CallbackWaiter; import ch.dissem.bitmessage.utils.TestBase; import org.junit.Test; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.junit.Assert.assertTrue; public class ProofOfWorkEngineTest extends TestBase { @@ -36,7 +36,7 @@ public class ProofOfWorkEngineTest extends TestBase { } private void testPOW(ProofOfWorkEngine engine) throws InterruptedException { - byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4}); + byte[] initialHash = cryptography().sha512(new byte[]{1, 3, 6, 4}); byte[] target = {0, 0, 0, -1, -1, -1, -1, -1}; final CallbackWaiter<byte[]> waiter1 = new CallbackWaiter<>(); @@ -49,10 +49,10 @@ public class ProofOfWorkEngineTest extends TestBase { }); byte[] nonce = waiter1.waitForValue(); System.out.println("Calculating nonce took " + waiter1.getTime() + "ms"); - assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8)); + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)); // Let's add a second (shorter) run to find possible multi threading issues - byte[] initialHash2 = security().sha512(new byte[]{1, 3, 6, 5}); + byte[] initialHash2 = cryptography().sha512(new byte[]{1, 3, 6, 5}); byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1}; final CallbackWaiter<byte[]> waiter2 = new CallbackWaiter<>(); @@ -65,7 +65,7 @@ public class ProofOfWorkEngineTest extends TestBase { }); byte[] nonce2 = waiter2.waitForValue(); System.out.println("Calculating nonce took " + waiter2.getTime() + "ms"); - assertTrue(Bytes.lt(security().doubleSha512(nonce2, initialHash2), target2, 8)); + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)); assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime()); } 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 0b5b142..7955e5b 100644 --- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java +++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java @@ -28,7 +28,7 @@ import ch.dissem.bitmessage.utils.Encode; import java.io.*; import static ch.dissem.bitmessage.utils.Decode.*; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * A {@link CustomMessage} implementation that contains signed and encrypted data. @@ -80,7 +80,7 @@ public class CryptoCustomMessage<T extends Streamable> extends CustomMessage { } data.write(out); - Encode.varBytes(security().getSignature(out.toByteArray(), identity.getPrivateKey()), out); + Encode.varBytes(cryptography().getSignature(out.toByteArray(), identity.getPrivateKey()), out); container = new CryptoBox(out.toByteArray(), publicKey); } @@ -138,7 +138,7 @@ public class CryptoCustomMessage<T extends Streamable> extends CustomMessage { } public void checkSignature(Pubkey pubkey) throws IOException, IllegalStateException { - if (!security().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { + if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { throw new IllegalStateException("Signature check failed"); } } 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 d451323..bf0fa6d 100644 --- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java +++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java @@ -30,7 +30,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.junit.Assert.assertEquals; public class CryptoCustomMessageTest extends TestBase { @@ -39,9 +39,9 @@ public class CryptoCustomMessageTest extends TestBase { PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - GenericPayload payloadBefore = new GenericPayload(0, 1, security().randomBytes(100)); + GenericPayload payloadBefore = new GenericPayload(0, 1, cryptography().randomBytes(100)); CryptoCustomMessage<GenericPayload> messageBefore = new CryptoCustomMessage<>(payloadBefore); - messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey())); + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); ByteArrayOutputStream out = new ByteArrayOutputStream(); messageBefore.write(out); @@ -65,11 +65,11 @@ public class CryptoCustomMessageTest extends TestBase { PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, security().randomBytes(64), + ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), ProofOfWorkRequest.Request.CALCULATE); CryptoCustomMessage<ProofOfWorkRequest> messageBefore = new CryptoCustomMessage<>(requestBefore); - messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey())); + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java index 54c8acf..4717675 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -46,7 +46,7 @@ import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT; import static ch.dissem.bitmessage.networking.Connection.Mode.SYNC; import static ch.dissem.bitmessage.networking.Connection.State.*; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; /** @@ -249,7 +249,7 @@ class Connection { } try { listener.receive(objectMessage); - security().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES); + cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES); ctx.getInventory().storeObject(objectMessage); // offer object to some random nodes so it gets distributed throughout the network: networkHandler.offer(objectMessage.getInventoryVector()); diff --git a/repositories/build.gradle b/repositories/build.gradle index abb8651..2ab092c 100644 --- a/repositories/build.gradle +++ b/repositories/build.gradle @@ -18,5 +18,6 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'com.h2database:h2:1.4.190' testCompile 'org.mockito:mockito-core:1.10.19' + testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } \ No newline at end of file diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java index 8f94d03..ae1b496 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -117,6 +117,24 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito return 0; } + @Override + public Plaintext getMessage(Object id) { + if (id instanceof Long) { + List<Plaintext> plaintexts = find("id=" + id); + switch (plaintexts.size()) { + case 0: + return null; + case 1: + return plaintexts.get(0); + default: + throw new ApplicationException("This shouldn't happen, found " + plaintexts.size() + + " messages, one or none was expected"); + } + } else { + throw new IllegalArgumentException("Long expected for ID"); + } + } + @Override public Plaintext getMessage(byte[] initialHash) { List<Plaintext> plaintexts = find("initial_hash=X'" + Strings.hex(initialHash) + "'"); diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java index 0fbb2dc..ac6e69f 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java @@ -1,6 +1,8 @@ package ch.dissem.bitmessage.repository; +import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.ProofOfWorkRepository; @@ -8,18 +10,21 @@ import ch.dissem.bitmessage.utils.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; import java.sql.*; import java.util.LinkedList; import java.util.List; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * @author Christian Basler */ -public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository { +public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository, InternalContext.ContextHolder { private static final Logger LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository.class); + private InternalContext ctx; public JdbcProofOfWorkRepository(JdbcConfig config) { super(config); @@ -30,17 +35,27 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork try ( Connection connection = config.getConnection(); PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + - "extra_bytes FROM POW WHERE initial_hash=?") + "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") ) { ps.setBytes(1, initialHash); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { Blob data = rs.getBlob("data"); - return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes") - ); + if (rs.getObject("message_id") == null) { + return new Item( + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes") + ); + } else { + return new Item( + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes"), + rs.getLong("expiration_time"), + ctx.getMessageRepository().getMessage(rs.getLong("message_id")) + ); + } } else { throw new IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash)); } @@ -70,24 +85,38 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork } @Override - public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { + public void putObject(Item item) { try ( Connection connection = config.getConnection(); PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + - "nonce_trials_per_byte, extra_bytes) VALUES (?, ?, ?, ?, ?)") + "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { - ps.setBytes(1, security().getInitialHash(object)); - writeBlob(ps, 2, object); - ps.setLong(3, object.getVersion()); - ps.setLong(4, nonceTrialsPerByte); - ps.setLong(5, extraBytes); + ps.setBytes(1, cryptography().getInitialHash(item.object)); + writeBlob(ps, 2, item.object); + ps.setLong(3, item.object.getVersion()); + ps.setLong(4, item.nonceTrialsPerByte); + ps.setLong(5, item.extraBytes); + + if (item.message == null) { + ps.setObject(6, null); + ps.setObject(7, null); + } else { + ps.setLong(6, item.expirationTime); + ps.setLong(7, (Long) item.message.getId()); + } ps.executeUpdate(); } catch (IOException | SQLException e) { - LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e); + LOG.debug("Error storing object of type " + item.object.getPayload().getClass().getSimpleName(), e); throw new ApplicationException(e); } } + @Override + public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { + putObject(new Item(object, nonceTrialsPerByte, extraBytes)); + } + @Override public void removeObject(byte[] initialHash) { try ( @@ -100,4 +129,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork LOG.debug(e.getMessage(), e); } } + + @Override + public void setContext(InternalContext context) { + this.ctx = context; + } } diff --git a/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql b/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql new file mode 100644 index 0000000..d67a1b5 --- /dev/null +++ b/repositories/src/main/resources/db/migration/V3.1__Update_table_POW.sql @@ -0,0 +1,2 @@ +ALTER TABLE POW ADD COLUMN expiration_time BIGINT; +ALTER TABLE POW ADD COLUMN message_id BIGINT; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java index 1975d43..7f3098b 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java @@ -68,9 +68,11 @@ public class JdbcAddressRepositoryTest extends TestBase { public void testFindIdentity() throws Exception { BitmessageAddress identity = new BitmessageAddress(IDENTITY_A); assertEquals(4, identity.getVersion()); - assertEquals(identity, repo.findIdentity(identity.getTag())); assertNull(repo.findContact(identity.getTag())); - assertTrue(identity.has(Pubkey.Feature.DOES_ACK)); + + BitmessageAddress storedIdentity = repo.findIdentity(identity.getTag()); + assertEquals(identity, storedIdentity); + assertTrue(storedIdentity.has(Pubkey.Feature.DOES_ACK)); } @Test diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java index 816eafa..81051de 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java @@ -32,7 +32,7 @@ import java.util.Arrays; import java.util.List; import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; @@ -54,7 +54,7 @@ public class JdbcMessageRepositoryTest extends TestBase { AddressRepository addressRepo = new JdbcAddressRepository(config); repo = new JdbcMessageRepository(config); new InternalContext(new BitmessageContext.Builder() - .cryptography(security()) + .cryptography(cryptography()) .addressRepo(addressRepo) .messageRepo(repo) ); @@ -146,7 +146,7 @@ public class JdbcMessageRepositoryTest extends TestBase { @Test public void testSave() throws Exception { Plaintext message = new Plaintext.Builder(MSG) - .IV(new InventoryVector(security().randomBytes(32))) + .IV(new InventoryVector(cryptography().randomBytes(32))) .from(identity) .to(contactA) .message("Subject", "Message") @@ -169,7 +169,7 @@ public class JdbcMessageRepositoryTest extends TestBase { public void testUpdate() throws Exception { List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); Plaintext message = messages.get(0); - message.setInventoryVector(new InventoryVector(security().randomBytes(32))); + message.setInventoryVector(new InventoryVector(cryptography().randomBytes(32))); repo.save(message); messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java index c8fbaf0..4396eb5 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java @@ -16,13 +16,24 @@ package ch.dissem.bitmessage.repository; +import ch.dissem.bitmessage.BitmessageContext; +import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.GenericPayload; import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository; +import ch.dissem.bitmessage.ports.AddressRepository; +import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; +import ch.dissem.bitmessage.utils.TestUtils; +import ch.dissem.bitmessage.utils.UnixTime; import org.junit.Before; import org.junit.Test; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; +import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -33,17 +44,51 @@ import static org.junit.Assert.assertTrue; public class JdbcProofOfWorkRepositoryTest extends TestBase { private TestJdbcConfig config; private JdbcProofOfWorkRepository repo; + private AddressRepository addressRepo; + private MessageRepository messageRepo; + + private byte[] initialHash1; + private byte[] initialHash2; @Before public void setUp() throws Exception { config = new TestJdbcConfig(); config.reset(); + addressRepo = new JdbcAddressRepository(config); + messageRepo = new JdbcMessageRepository(config); repo = new JdbcProofOfWorkRepository(config); + InternalContext ctx = new InternalContext(new BitmessageContext.Builder() + .addressRepo(addressRepo) + .messageRepo(messageRepo) + .powRepo(repo) + .cryptography(cryptography()) + ); repo.putObject(new ObjectMessage.Builder() .payload(new GetPubkey(new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), 1000, 1000); + initialHash1 = repo.getItems().get(0); + + BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + BitmessageAddress recipient = TestUtils.loadContact(); + addressRepo.save(sender); + addressRepo.save(recipient); + Plaintext plaintext = new Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build(); + messageRepo.save(plaintext); + initialHash2 = cryptography().getInitialHash(plaintext.getAckMessage()); + repo.putObject(new Item( + plaintext.getAckMessage(), + 1000, 1000, + UnixTime.now(+10 * MINUTE), + plaintext + )); } @Test @@ -55,16 +100,52 @@ public class JdbcProofOfWorkRepositoryTest extends TestBase { assertThat(repo.getItems().size(), is(sizeBefore + 1)); } + @Test + public void ensureAckObjectsAreStored() throws Exception { + int sizeBefore = repo.getItems().size(); + BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); + BitmessageAddress recipient = TestUtils.loadContact(); + addressRepo.save(sender); + addressRepo.save(recipient); + Plaintext plaintext = new Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build(); + messageRepo.save(plaintext); + repo.putObject(new Item( + plaintext.getAckMessage(), + 1000, 1000, + UnixTime.now(+10 * MINUTE), + plaintext + )); + assertThat(repo.getItems().size(), is(sizeBefore + 1)); + } + @Test public void ensureItemCanBeRetrieved() { - byte[] initialHash = repo.getItems().get(0); - ProofOfWorkRepository.Item item = repo.getItem(initialHash); + Item item = repo.getItem(initialHash1); assertThat(item, notNullValue()); assertThat(item.object.getPayload(), instanceOf(GetPubkey.class)); assertThat(item.nonceTrialsPerByte, is(1000L)); assertThat(item.extraBytes, is(1000L)); } + @Test + public void ensureAckItemCanBeRetrieved() { + Item item = repo.getItem(initialHash2); + assertThat(item, notNullValue()); + assertThat(item.object.getPayload(), instanceOf(GenericPayload.class)); + assertThat(item.nonceTrialsPerByte, is(1000L)); + assertThat(item.extraBytes, is(1000L)); + assertThat(item.expirationTime, not(0)); + assertThat(item.message, notNullValue()); + assertThat(item.message.getFrom().getPrivateKey(), notNullValue()); + assertThat(item.message.getTo().getPubkey(), notNullValue()); + } + @Test(expected = RuntimeException.class) public void ensureRetrievingNonexistingItemThrowsException() { repo.getItem(new byte[0]); @@ -72,8 +153,8 @@ public class JdbcProofOfWorkRepositoryTest extends TestBase { @Test public void ensureItemCanBeDeleted() { - byte[] initialHash = repo.getItems().get(0); - repo.removeObject(initialHash); + repo.removeObject(initialHash1); + repo.removeObject(initialHash2); assertTrue(repo.getItems().isEmpty()); } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java index ad6098e..3f4b370 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java @@ -27,7 +27,7 @@ import java.io.*; import java.util.Collection; import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * @author Christian Basler @@ -77,7 +77,7 @@ public class WifExporter { byte[] result = new byte[37]; result[0] = (byte) 0x80; System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE); - byte[] hash = security().doubleSha256(result, PRIVATE_KEY_SIZE + 1); + byte[] hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1); System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4); return Base58.encode(result); } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java index 1bd6bcd..f5a8ddc 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java @@ -31,7 +31,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; -import static ch.dissem.bitmessage.utils.Singleton.security; +import static ch.dissem.bitmessage.utils.Singleton.cryptography; /** * @author Christian Basler @@ -87,7 +87,7 @@ public class WifImporter { throw new IOException("Unknown format: " + WIF_SECRET_LENGTH + " bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); - byte[] hash = security().doubleSha256(bytes, 33); + byte[] hash = cryptography().doubleSha256(bytes, 33); for (int i = 0; i < 4; i++) { if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); }