Jabit/core/src/main/kotlin/ch/dissem/bitmessage/entity/BitmessageAddress.kt

196 lines
7.2 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
import ch.dissem.bitmessage.entity.payload.Pubkey
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature
import ch.dissem.bitmessage.entity.payload.V4Pubkey
import ch.dissem.bitmessage.entity.valueobject.PrivateKey
import ch.dissem.bitmessage.utils.AccessCounter
import ch.dissem.bitmessage.utils.Base58
import ch.dissem.bitmessage.utils.Bytes
import ch.dissem.bitmessage.utils.Decode.bytes
import ch.dissem.bitmessage.utils.Decode.varInt
import ch.dissem.bitmessage.utils.Encode
import ch.dissem.bitmessage.utils.Singleton.cryptography
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.Serializable
import java.util.*
/**
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
* holding private keys.
*/
class BitmessageAddress : Serializable {
val version: Long
val stream: Long
val ripe: ByteArray
val tag: ByteArray?
/**
* The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create
* it regardless of address version.
*/
val publicDecryptionKey: ByteArray
val address: String
var privateKey: PrivateKey? = null
private set
var pubkey: Pubkey? = null
set(pubkey) {
if (pubkey != null) {
if (pubkey is V4Pubkey) {
if (!Arrays.equals(tag, pubkey.tag))
throw IllegalArgumentException("Pubkey has incompatible tag")
}
if (!Arrays.equals(ripe, pubkey.ripe))
throw IllegalArgumentException("Pubkey has incompatible ripe")
field = pubkey
}
}
var alias: String? = null
var isSubscribed: Boolean = false
var isChan: Boolean = false
internal constructor(version: Long, stream: Long, ripe: ByteArray) {
this.version = version
this.stream = stream
this.ripe = ripe
val os = ByteArrayOutputStream()
Encode.varInt(version, os)
Encode.varInt(stream, os)
if (version < 4) {
val 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
val 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
val offset = Bytes.numberOfLeadingZeros(ripe)
os.write(ripe, offset, ripe.size - offset)
val checksum = cryptography().doubleSha512(os.toByteArray())
os.write(checksum, 0, 4)
this.address = "BM-" + Base58.encode(os.toByteArray())
}
constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) {
this.pubkey = publicKey
}
constructor(address: String, passphrase: String) : this(address) {
val key = PrivateKey(this, passphrase)
if (!Arrays.equals(ripe, key.pubkey.ripe)) {
throw IllegalArgumentException("Wrong address or passphrase")
}
this.privateKey = key
this.pubkey = key.pubkey
}
constructor(privateKey: PrivateKey) : this(privateKey.pubkey) {
this.privateKey = privateKey
}
constructor(address: String) {
this.address = address
val bytes = Base58.decode(address.substring(3))
val `in` = ByteArrayInputStream(bytes)
val counter = AccessCounter()
this.version = varInt(`in`, counter)
this.stream = varInt(`in`, counter)
this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20)
// test checksum
var checksum = cryptography().doubleSha512(bytes, bytes.size - 4)
val expectedChecksum = bytes(`in`, 4)
for (i in 0..3) {
if (expectedChecksum[i] != checksum[i])
throw IllegalArgumentException("Checksum of address failed")
}
if (version < 4) {
checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
this.tag = null
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
} else {
checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe)
this.tag = Arrays.copyOfRange(checksum, 32, 64)
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32)
}
}
override fun toString(): String {
return alias ?: address
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BitmessageAddress) return false
return version == other.version &&
stream == other.stream &&
Arrays.equals(ripe, other.ripe)
}
override fun hashCode(): Int {
return Arrays.hashCode(ripe)
}
fun has(feature: Feature?): Boolean {
return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false
}
companion object {
@JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress {
val result = BitmessageAddress(address, passphrase)
result.isChan = true
return result
}
@JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress {
val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase)
val result = BitmessageAddress(privateKey)
result.isChan = true
return result
}
@JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int,
version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> {
val result = ArrayList<BitmessageAddress>(numberOfAddresses)
val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter)
for (pk in privateKeys) {
result.add(BitmessageAddress(pk))
}
return result
}
@JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray {
val out = ByteArrayOutputStream()
Encode.varInt(version, out)
Encode.varInt(stream, out)
out.write(ripe)
return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64)
}
}
}