diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt index 2eaf6e0..56bcdee 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt @@ -37,33 +37,33 @@ import java.util.* data class Message constructor( val subject: String, val body: String, - val parents: List, - val files: List + val parents: List = emptyList(), + val files: List = emptyList() ) : ExtendedEncoding.ExtendedType { override val type: String = TYPE override fun pack(): MPMap> { val result = MPMap>() - result.put("".mp, TYPE.mp) - result.put("subject".mp, subject.mp) - result.put("body".mp, body.mp) + result["".mp] = TYPE.mp + result["subject".mp] = subject.mp + result["body".mp] = body.mp if (!files.isEmpty()) { val items = MPArray>>() - result.put("files".mp, items) + result["files".mp] = items for (file in files) { val item = MPMap>() - item.put("name".mp, file.name.mp) - item.put("data".mp, file.data.mp) - item.put("type".mp, file.type.mp) - item.put("disposition".mp, file.disposition.name.mp) + item["name".mp] = file.name.mp + item["data".mp] = file.data.mp + item["type".mp] = file.type.mp + item["disposition".mp] = file.disposition.name.mp items.add(item) } } if (!parents.isEmpty()) { val items = MPArray() - result.put("parents".mp, items) + result["parents".mp] = items for ((hash) in parents) { items.add(mp(*hash)) } @@ -179,6 +179,6 @@ data class Message constructor( companion object { private val LOG = LoggerFactory.getLogger(Message::class.java) - val TYPE = "message" + const val TYPE = "message" } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt index 32f7dee..2e3c9a6 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt @@ -156,6 +156,16 @@ interface Cryptography { fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long, callback: ProofOfWorkEngine.Callback) + @JvmSynthetic + fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, + extraBytes: Long, callback: (ByteArray, ByteArray) -> Unit) { + doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + callback.invoke(initialHash, nonce) + } + }) + } + /** * @param objectMessage to be checked * * diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt index ce76694..3cc82b3 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt @@ -23,16 +23,32 @@ interface ProofOfWorkEngine { /** * Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long * smaller than target. - + * * @param initialHash the SHA-512 hash of the object to send, sans nonce - * * * @param target the target, representing an unsigned long - * * - * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make - * * sure this is only called once. + * @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine + * implementation must make sure this is only called once. */ fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback) + /** + * Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long + * smaller than target. + * + * @param initialHash the SHA-512 hash of the object to send, sans nonce + * @param target the target, representing an unsigned long + * @param callback called with the initial hash and the calculated nonce as argument. The ProofOfWorkEngine + * implementation must make sure this is only called once. + */ + @JvmSynthetic + fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: (ByteArray, ByteArray) -> Unit) { + calculateNonce(initialHash, target, object : Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + callback.invoke(initialHash, nonce) + } + }) + } + interface Callback { /** * @param nonce 8 bytes nonce diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt index dca4744..043f811 100644 --- a/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -126,7 +126,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact)) - verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any()) + verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any()) } @Test @@ -139,7 +139,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, times(1)).save(contact) - verify(testPowEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -162,7 +162,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, atLeastOnce()).save(contact) - verify(testPowEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -186,7 +186,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, atLeastOnce()).save(any()) - verify(testPowEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -245,7 +245,7 @@ class BitmessageContextTest { "Subject", "Message") verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce()) .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) - verify(testPowEngine).calculateNonce(any(), any(), any()) + verify(testPowEngine).calculateNonce(any(), any(), any()) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) } diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt index c244ce0..276f4ab 100644 --- a/core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt @@ -23,6 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.MSG import ch.dissem.bitmessage.entity.payload.GenericPayload import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.ports.Cryptography +import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkRepository import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.TestUtils @@ -65,11 +66,11 @@ class ProofOfWorkServiceTest { fun `ensure missing proof of work is done`() { whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64))) whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002)) - doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any()) + doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any<ProofOfWorkEngine.Callback>()) ctx.proofOfWorkService.doMissingProofOfWork(10) - verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any()) + verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any<ProofOfWorkEngine.Callback>()) } @Test diff --git a/cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt index dbd4980..6d4a05d 100644 --- a/cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt +++ b/cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -89,12 +89,7 @@ class CryptographyTest { stream = 1 ) val waiter = CallbackWaiter<ByteArray>() - crypto.doProofOfWork(objectMessage, 1000, 1000, - object : ProofOfWorkEngine.Callback { - override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { - waiter.setValue(nonce) - } - }) + crypto.doProofOfWork(objectMessage, 1000, 1000) { _, nonce -> waiter.setValue(nonce) } objectMessage.nonce = waiter.waitForValue() try { crypto.checkProofOfWork(objectMessage, 1000, 1000) diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt index 45024f4..3b71ea7 100644 --- a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt @@ -42,19 +42,21 @@ import java.io.OutputStream */ class CryptoCustomMessage<T : Streamable> : CustomMessage { - private val dataReader: Reader<T>? + private val dataReader: (BitmessageAddress, InputStream) -> T private var container: CryptoBox? = null var sender: BitmessageAddress? = null private set private var data: T? = null - private set constructor(data: T) : super(COMMAND, null) { this.data = data - this.dataReader = null + this.dataReader = { _, _ -> data } } - private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) { + private constructor(container: CryptoBox, dataReader: (BitmessageAddress, InputStream) -> T) : super( + COMMAND, + null + ) { this.container = container this.dataReader = dataReader } @@ -81,8 +83,9 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { @Throws(DecryptionFailedException::class) fun decrypt(privateKey: ByteArray): T { - val input = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")) - if (dataReader == null) throw IllegalStateException("no data reader available") + val input = SignatureCheckingInputStream( + container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available") + ) val addressVersion = varInt(input) val stream = varInt(input) @@ -92,18 +95,20 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { val nonceTrialsPerByte = if (addressVersion >= 3) varInt(input) else 0 val extraBytes = if (addressVersion >= 3) varInt(input) else 0 - val sender = BitmessageAddress(Factory.createPubkey( - addressVersion, - stream, - publicSigningKey, - publicEncryptionKey, - nonceTrialsPerByte, - extraBytes, - behaviorBitfield - )) + val sender = BitmessageAddress( + Factory.createPubkey( + addressVersion, + stream, + publicSigningKey, + publicEncryptionKey, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + ) + ) this.sender = sender - data = dataReader.read(sender, input) + data = dataReader.invoke(sender, input) input.checkSignature(sender.pubkey!!) @@ -127,7 +132,8 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { fun read(sender: BitmessageAddress, input: InputStream): T } - private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() { + private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : + InputStream() { private val out = ByteArrayOutputStream() override fun read(): Int { @@ -145,13 +151,24 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { } companion object { - @JvmField - val COMMAND = "ENCRYPTED" + const val COMMAND = "ENCRYPTED" @JvmStatic - fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> { - val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) - return CryptoCustomMessage(cryptoBox, dataReader) - } + fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> = + CryptoCustomMessage( + CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) + ) { address, input -> + dataReader.read(address, input) + } + + @JvmSynthetic + fun <T : Streamable> read( + data: CustomMessage, + dataReader: (BitmessageAddress, InputStream) -> T + ): CryptoCustomMessage<T> = + CryptoCustomMessage( + CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size), + dataReader + ) } }