208 lines
7.6 KiB
Kotlin
208 lines
7.6 KiB
Kotlin
/*
|
|
* Copyright 2017 Christian Basler
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package ch.dissem.bitmessage.ports
|
|
|
|
import ch.dissem.bitmessage.InternalContext
|
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES
|
|
import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE
|
|
import ch.dissem.bitmessage.entity.ObjectMessage
|
|
import ch.dissem.bitmessage.entity.payload.Pubkey
|
|
import ch.dissem.bitmessage.exception.ApplicationException
|
|
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException
|
|
import ch.dissem.bitmessage.factory.Factory
|
|
import ch.dissem.bitmessage.utils.Bytes
|
|
import ch.dissem.bitmessage.utils.UnixTime
|
|
import ch.dissem.bitmessage.utils.max
|
|
import org.slf4j.LoggerFactory
|
|
import java.math.BigInteger
|
|
import java.security.*
|
|
import javax.crypto.Mac
|
|
import javax.crypto.spec.SecretKeySpec
|
|
|
|
/**
|
|
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
|
*/
|
|
abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography, InternalContext.ContextHolder {
|
|
private lateinit var ctx: InternalContext
|
|
|
|
@JvmField protected val ALGORITHM_ECDSA = "ECDSA"
|
|
@JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"
|
|
@JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA"
|
|
|
|
override fun setContext(context: InternalContext) {
|
|
ctx = context
|
|
}
|
|
|
|
override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray {
|
|
val mda = md("SHA-512")
|
|
mda.update(data, offset, length)
|
|
return mda.digest()
|
|
}
|
|
|
|
override fun sha512(vararg data: ByteArray): ByteArray {
|
|
return hash("SHA-512", *data)
|
|
}
|
|
|
|
override fun doubleSha512(vararg data: ByteArray): ByteArray {
|
|
val mda = md("SHA-512")
|
|
for (d in data) {
|
|
mda.update(d)
|
|
}
|
|
return mda.digest(mda.digest())
|
|
}
|
|
|
|
override fun doubleSha512(data: ByteArray, length: Int): ByteArray {
|
|
val mda = md("SHA-512")
|
|
mda.update(data, 0, length)
|
|
return mda.digest(mda.digest())
|
|
}
|
|
|
|
override fun ripemd160(vararg data: ByteArray): ByteArray {
|
|
return hash("RIPEMD160", *data)
|
|
}
|
|
|
|
override fun doubleSha256(data: ByteArray, length: Int): ByteArray {
|
|
val mda = md("SHA-256")
|
|
mda.update(data, 0, length)
|
|
return mda.digest(mda.digest())
|
|
}
|
|
|
|
override fun sha1(vararg data: ByteArray): ByteArray {
|
|
return hash("SHA-1", *data)
|
|
}
|
|
|
|
override fun randomBytes(length: Int): ByteArray {
|
|
val result = ByteArray(length)
|
|
RANDOM.nextBytes(result)
|
|
return result
|
|
}
|
|
|
|
override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long,
|
|
extraBytes: Long, callback: ProofOfWorkEngine.Callback) {
|
|
|
|
val initialHash = getInitialHash(objectMessage)
|
|
|
|
val target = getProofOfWorkTarget(objectMessage,
|
|
max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES))
|
|
|
|
ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback)
|
|
}
|
|
|
|
@Throws(InsufficientProofOfWorkException::class)
|
|
override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) {
|
|
val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes)
|
|
val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage))
|
|
if (Bytes.lt(target, value, 8)) {
|
|
throw InsufficientProofOfWorkException(target, value)
|
|
}
|
|
}
|
|
|
|
protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray {
|
|
// TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network
|
|
val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider)
|
|
sig.initSign(privKey)
|
|
sig.update(data)
|
|
return sig.sign()
|
|
}
|
|
|
|
|
|
protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean {
|
|
for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) {
|
|
val sig = Signature.getInstance(algorithm, provider)
|
|
sig.initVerify(publicKey)
|
|
sig.update(data)
|
|
if (sig.verify(signature)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
override fun getInitialHash(objectMessage: ObjectMessage): ByteArray {
|
|
return sha512(objectMessage.payloadBytesWithoutNonce)
|
|
}
|
|
|
|
override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray {
|
|
@Suppress("NAME_SHADOWING")
|
|
val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte
|
|
@Suppress("NAME_SHADOWING")
|
|
val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes
|
|
|
|
val TTL = BigInteger.valueOf(objectMessage.expiresTime - UnixTime.now)
|
|
val powLength = BigInteger.valueOf(objectMessage.payloadBytesWithoutNonce.size + extraBytes)
|
|
val denominator = BigInteger.valueOf(nonceTrialsPerByte)
|
|
.multiply(
|
|
powLength.add(
|
|
powLength.multiply(TTL).divide(TWO_POW_16)
|
|
)
|
|
)
|
|
return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8)
|
|
}
|
|
|
|
private fun hash(algorithm: String, vararg data: ByteArray): ByteArray {
|
|
val mda = md(algorithm)
|
|
for (d in data) {
|
|
mda.update(d)
|
|
}
|
|
return mda.digest()
|
|
}
|
|
|
|
private fun md(algorithm: String): MessageDigest {
|
|
try {
|
|
return MessageDigest.getInstance(algorithm, provider)
|
|
} catch (e: GeneralSecurityException) {
|
|
throw ApplicationException(e)
|
|
}
|
|
|
|
}
|
|
|
|
override fun mac(key_m: ByteArray, data: ByteArray): ByteArray {
|
|
try {
|
|
val mac = Mac.getInstance("HmacSHA256", provider)
|
|
mac.init(SecretKeySpec(key_m, "HmacSHA256"))
|
|
return mac.doFinal(data)
|
|
} catch (e: GeneralSecurityException) {
|
|
throw ApplicationException(e)
|
|
}
|
|
|
|
}
|
|
|
|
override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray,
|
|
nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey {
|
|
return Factory.createPubkey(version, stream,
|
|
createPublicKey(privateSigningKey),
|
|
createPublicKey(privateEncryptionKey),
|
|
nonceTrialsPerByte, extraBytes, *features)
|
|
}
|
|
|
|
override fun keyToBigInt(privateKey: ByteArray): BigInteger {
|
|
return BigInteger(1, privateKey)
|
|
}
|
|
|
|
override fun randomNonce(): Long {
|
|
return RANDOM.nextLong()
|
|
}
|
|
|
|
companion object {
|
|
protected val LOG = LoggerFactory.getLogger(Cryptography::class.java)
|
|
private val RANDOM = SecureRandom()
|
|
private val TWO = BigInteger.valueOf(2)
|
|
private val TWO_POW_64 = TWO.pow(64)
|
|
private val TWO_POW_16 = TWO.pow(16)
|
|
}
|
|
}
|