Jabit/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt

170 lines
6.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.entity.valueobject
import ch.dissem.bitmessage.InternalContext
import ch.dissem.bitmessage.entity.BitmessageAddress
import ch.dissem.bitmessage.entity.Streamable
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.exception.ApplicationException
import ch.dissem.bitmessage.factory.Factory
import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.Decode
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.*
import java.nio.ByteBuffer
import java.util.*
/**
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
* [Pubkey] object.
*/
class PrivateKey : Streamable {
val privateSigningKey: ByteArray
val privateEncryptionKey: ByteArray
val pubkey: Pubkey
constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) {
var privSK: ByteArray
var pubSK: ByteArray
var privEK: ByteArray
var pubEK: ByteArray
var ripe: ByteArray
do {
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].toInt() != 0 || shorter && ripe[1].toInt() != 0)
this.privateSigningKey = privSK
this.privateEncryptionKey = privEK
this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
nonceTrialsPerByte, extraBytes, *features)
}
constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) {
this.privateSigningKey = privateSigningKey
this.privateEncryptionKey = privateEncryptionKey
this.pubkey = pubkey
}
constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase)
constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate())
private constructor(builder: Builder) {
this.privateSigningKey = builder.privSK!!
this.privateEncryptionKey = builder.privEK!!
this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!,
InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES)
}
private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) {
internal var seed: ByteArray? = null
internal var nextNonce: Long = 0
internal var privSK: ByteArray? = null
internal var privEK: ByteArray? = null
internal var pubSK: ByteArray? = null
internal var pubEK: ByteArray? = null
internal fun seed(passphrase: String): Builder {
try {
seed = passphrase.toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
throw ApplicationException(e)
}
return this
}
internal fun generate(): Builder {
var signingKeyNonce = nextNonce
var encryptionKeyNonce = nextNonce + 1
var ripe: ByteArray
do {
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
} while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0)
nextNonce = signingKeyNonce
return this
}
}
override fun write(out: OutputStream) {
Encode.varInt(pubkey.version, out)
Encode.varInt(pubkey.stream, out)
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varInt(baos.size(), out)
out.write(baos.toByteArray())
Encode.varBytes(privateSigningKey, out)
Encode.varBytes(privateEncryptionKey, out)
}
override fun write(buffer: ByteBuffer) {
Encode.varInt(pubkey.version, buffer)
Encode.varInt(pubkey.stream, buffer)
try {
val baos = ByteArrayOutputStream()
pubkey.writeUnencrypted(baos)
Encode.varBytes(baos.toByteArray(), buffer)
} catch (e: IOException) {
throw ApplicationException(e)
}
Encode.varBytes(privateSigningKey, buffer)
Encode.varBytes(privateEncryptionKey, buffer)
}
companion object {
@JvmField val PRIVATE_KEY_SIZE = 32
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> {
val result = ArrayList<PrivateKey>(numberOfAddresses)
val builder = Builder(version, stream, shorter).seed(passphrase)
for (i in 0..numberOfAddresses - 1) {
builder.generate()
result.add(PrivateKey(builder))
}
return result
}
@JvmStatic fun read(`is`: InputStream): PrivateKey {
val version = Decode.varInt(`is`).toInt()
val stream = Decode.varInt(`is`)
val len = Decode.varInt(`is`).toInt()
val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered")
val signingKey = Decode.varBytes(`is`)
val encryptionKey = Decode.varBytes(`is`)
return PrivateKey(signingKey, encryptionKey, pubkey)
}
}
}