crypto2 as a separate library
This commit is contained in:
parent
67c0009b5b
commit
ae3af68dab
@ -13,6 +13,7 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
maven("https://maven.universablockchain.com/")
|
maven("https://maven.universablockchain.com/")
|
||||||
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@ -67,6 +68,7 @@ kotlin {
|
|||||||
|
|
||||||
api("net.sergeych:mp_bintools:0.0.6-SNAPSHOT")
|
api("net.sergeych:mp_bintools:0.0.6-SNAPSHOT")
|
||||||
api("net.sergeych:mp_stools:1.4.1")
|
api("net.sergeych:mp_stools:1.4.1")
|
||||||
|
api("net.sergeych:crypto2:0.1.1-SNAPSHOT")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.LibsodiumInitializer
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
|
|
||||||
private var isReady = false
|
|
||||||
private val readyAccess = Mutex()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Library initialization: should be called before all other calls.
|
|
||||||
* It is safe and with little performance penalty to call it multiple times.
|
|
||||||
*/
|
|
||||||
suspend fun initCrypto() {
|
|
||||||
// faster to check with no lock
|
|
||||||
if( !isReady) {
|
|
||||||
readyAccess.withLock {
|
|
||||||
// recheck with lock, it could be ready by now
|
|
||||||
if( !isReady ) {
|
|
||||||
LibsodiumInitializer.initialize()
|
|
||||||
isReady = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Seal(
|
|
||||||
val publicKey: SigningKey.Public,
|
|
||||||
val signature: UByteArray
|
|
||||||
) {
|
|
||||||
inline fun verify(message: UByteArray) = publicKey.verify(signature, message)
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multi-signed data box. Use [SignedBox.invoke] to easily create
|
|
||||||
* instances and [SignedBox.plus] to add more signatures (signing keys), and
|
|
||||||
* [SignedBox.contains] to check for a specific key signature presence.
|
|
||||||
*
|
|
||||||
* It is serializable and checks integrity on deserialization. If any of seals does not
|
|
||||||
* match the signed [message], it throws [IllegalSignatureException] _on deserialization_.
|
|
||||||
* E.g., if you have it deserialized, it is ok, check it contains all needed keys among
|
|
||||||
* signers.
|
|
||||||
*
|
|
||||||
* __The main constructor is used for deserializing only__. Don't use it directly unless you
|
|
||||||
* know what you are doing as it may be dangerous.Use one of the above to create or change it.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
class SignedBox(
|
|
||||||
val message: UByteArray,
|
|
||||||
private val seals: List<Seal>,
|
|
||||||
@Transient
|
|
||||||
private val checkOnInit: Boolean = true
|
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this instance is not signed by a given key, return new instance signed also by this
|
|
||||||
* key, or return unchanged (same) object if it is already signed by this key; you
|
|
||||||
* _can't assume it always returns a copied object!_
|
|
||||||
*/
|
|
||||||
operator fun plus(key: SigningKey.Secret): SignedBox =
|
|
||||||
if (key.publicKey in this) this
|
|
||||||
else SignedBox(message, seals + key.seal(message), false)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check that it is signed with a specified key.
|
|
||||||
*/
|
|
||||||
operator fun contains(publicKey: SigningKey.Public): Boolean {
|
|
||||||
return seals.any { it.publicKey == publicKey }
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
|
|
||||||
if (checkOnInit) {
|
|
||||||
if (!seals.all { it.verify(message) }) throw IllegalSignatureException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
/**
|
|
||||||
* Create a new instance with a specific data sealed by one or more
|
|
||||||
* keys. At least one key is required to disallow providing not-signed
|
|
||||||
* instances, e.g. [SignedBox] is guaranteed to be properly sealed when
|
|
||||||
* successfully instantiated.
|
|
||||||
*
|
|
||||||
* @param data a message to sign
|
|
||||||
* @param keys a list of keys to sign with, should be at least one key.
|
|
||||||
* @throws IllegalArgumentException if keys are not specified.
|
|
||||||
*/
|
|
||||||
operator fun invoke(data: UByteArray, vararg keys: SigningKey.Secret): SignedBox =
|
|
||||||
SignedBox(data, keys.map { it.seal(data) }, false)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
|
|
||||||
import com.ionspin.kotlin.crypto.signature.Signature
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.crypto2.SigningKey.Secret
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keys in general: public, secret and later symmetric too.
|
|
||||||
* Keys could be compared to each other for equality and used
|
|
||||||
* as a Map keys (not sure about js).
|
|
||||||
*
|
|
||||||
* Use [Secret.pair] to create new keys.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
sealed class SigningKey {
|
|
||||||
abstract val packed: UByteArray
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return other is SigningKey && other.packed contentEquals packed
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return packed.contentHashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = packed.encodeToBase64Url()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Public key to verify signatures only
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("p")
|
|
||||||
class Public(override val packed: UByteArray) : SigningKey() {
|
|
||||||
/**
|
|
||||||
* Verify the signature and return true if it is correct.
|
|
||||||
*/
|
|
||||||
fun verify(signature: UByteArray, message: UByteArray): Boolean = try {
|
|
||||||
Signature.verifyDetached(signature, message, packed)
|
|
||||||
true
|
|
||||||
} catch (_: InvalidSignatureException) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "Pub:${super.toString()}"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secret key to sign only
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
@SerialName("s")
|
|
||||||
class Secret(override val packed: UByteArray) : SigningKey() {
|
|
||||||
|
|
||||||
val publicKey: Public by lazy {
|
|
||||||
Public(Signature.ed25519SkToPk(packed))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sign(message: UByteArray): UByteArray = Signature.detached(message, packed)
|
|
||||||
|
|
||||||
fun seal(message: UByteArray): Seal = Seal(this.publicKey, sign(message))
|
|
||||||
override fun toString(): String = "Sct:${super.toString()}"
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
data class Pair(val signing: Secret, val aPublic: Public)
|
|
||||||
|
|
||||||
fun pair(): Pair {
|
|
||||||
val p = Signature.keypair()
|
|
||||||
return Pair(Secret(p.secretKey), Public(p.publicKey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class IllegalSignatureException: RuntimeException("signed data is tampered or signature is corrupted")
|
|
@ -1,7 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import net.sergeych.bintools.CRC
|
|
||||||
|
|
||||||
fun isValidContrail(data: UByteArray): Boolean = CRC.crc8(data.copyOfRange(1, data.size)) == data[0]
|
|
||||||
|
|
||||||
fun createContrail(data: UByteArray): UByteArray = ubyteArrayOf(CRC.crc8(data)) + data
|
|
@ -1,111 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
|
|
||||||
import com.ionspin.kotlin.crypto.secretbox.crypto_secretbox_NONCEBYTES
|
|
||||||
import com.ionspin.kotlin.crypto.util.LibsodiumRandom
|
|
||||||
import kotlinx.coroutines.channels.ReceiveChannel
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import net.sergeych.bintools.toDataSource
|
|
||||||
import net.sergeych.bipack.BipackDecoder
|
|
||||||
import net.sergeych.bipack.BipackEncoder
|
|
||||||
|
|
||||||
class DecryptionFailedException : RuntimeException("can't encrypt: wrong key or tampered message")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class WithNonce(
|
|
||||||
val cipherData: UByteArray,
|
|
||||||
val nonce: UByteArray,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class WithFill(
|
|
||||||
val data: UByteArray,
|
|
||||||
val safetyFill: UByteArray? = null
|
|
||||||
) {
|
|
||||||
constructor(data: UByteArray, fillSize: Int) : this(data, randomBytes(fillSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun readVarUnsigned(input: ReceiveChannel<UByte>): UInt {
|
|
||||||
var result = 0u
|
|
||||||
var cnt = 0
|
|
||||||
while(true) {
|
|
||||||
val b = input.receive().toUInt()
|
|
||||||
result = (result shl 7) or (b and 0x7fu)
|
|
||||||
if( (b and 0x80u) != 0u ) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
if( ++cnt > 5 ) throw IllegalArgumentException("overflow while decoding varuint")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun encodeVarUnsigned(value: UInt): UByteArray {
|
|
||||||
val result = mutableListOf<UByte>()
|
|
||||||
var rest = value
|
|
||||||
do {
|
|
||||||
val mask = if( rest <= 0x7fu ) 0x80u else 0u
|
|
||||||
result.add( (mask or (rest and 0x7fu)).toUByte() )
|
|
||||||
rest = rest shr 7
|
|
||||||
} while(rest != 0u)
|
|
||||||
return result.toUByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun randomBytes(n: Int): UByteArray = if (n > 0) LibsodiumRandom.buf(n) else ubyteArrayOf()
|
|
||||||
|
|
||||||
fun randomBytes(n: UInt): UByteArray = if (n > 0u) LibsodiumRandom.buf(n.toInt()) else ubyteArrayOf()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uniform random in `0 ..< max` range
|
|
||||||
*/
|
|
||||||
fun randomUInt(max: UInt) = LibsodiumRandom.uniform(max)
|
|
||||||
fun randomUInt(max: Int) = LibsodiumRandom.uniform(max.toUInt())
|
|
||||||
|
|
||||||
fun <T: Comparable<T>>T.limit(range: ClosedRange<T>) = when {
|
|
||||||
this < range.start -> range.start
|
|
||||||
this > range.endInclusive -> range.endInclusive
|
|
||||||
else -> this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T: Comparable<T>>T.limitMax(max: T) = if( this < max ) this else max
|
|
||||||
fun <T: Comparable<T>>T.limitMin(min: T) = if( this > min ) this else min
|
|
||||||
|
|
||||||
fun randomNonce(): UByteArray = randomBytes(crypto_secretbox_NONCEBYTES)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Secret-key encrypt with authentication.
|
|
||||||
* Generates random nonce and add some random fill to protect
|
|
||||||
* against some analysis attacks. Nonce is included in the result. To be
|
|
||||||
* used with [decrypt].
|
|
||||||
* @param secretKey a _secret_ key, see [SecretBox.keygen()] or like.
|
|
||||||
* @param plain data to encrypt
|
|
||||||
* @param fillSize number of random fill data to add. Use random value or default.
|
|
||||||
*/
|
|
||||||
fun encrypt(
|
|
||||||
secretKey: UByteArray,
|
|
||||||
plain: UByteArray,
|
|
||||||
fillSize: Int = randomUInt((plain.size * 3 / 10).limitMin(3)).toInt()
|
|
||||||
): UByteArray {
|
|
||||||
val filled = BipackEncoder.encode(WithFill(plain, fillSize))
|
|
||||||
val nonce = randomNonce()
|
|
||||||
val encrypted = SecretBox.easy(filled.toUByteArray(), nonce, secretKey)
|
|
||||||
return BipackEncoder.encode(WithNonce(encrypted, nonce)).toUByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decrypt a secret-key-based message, normally encrypted with [encrypt].
|
|
||||||
* @throws DecryptionFailedException if the key is wrong or a message is tampered with (MAC
|
|
||||||
* check failed).
|
|
||||||
*/
|
|
||||||
fun decrypt(secretKey: UByteArray, cipher: UByteArray): UByteArray {
|
|
||||||
val wn: WithNonce = BipackDecoder.decode(cipher.toDataSource())
|
|
||||||
try {
|
|
||||||
return BipackDecoder.decode<WithFill>(
|
|
||||||
SecretBox.openEasy(wn.cipherData, wn.nonce, secretKey).toDataSource()
|
|
||||||
).data
|
|
||||||
}
|
|
||||||
catch(_: com.ionspin.kotlin.crypto.secretbox.SecretBoxCorruptedOrTamperedDataExceptionOrInvalidKey) {
|
|
||||||
throw DecryptionFailedException()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package net.sergeych.crypto2
|
|
||||||
|
|
||||||
import net.sergeych.bintools.toDump
|
|
||||||
import net.sergeych.mp_tools.encodeToBase64Url
|
|
||||||
|
|
||||||
fun UByteArray.toDump(wide: Boolean = false) = toByteArray().toDump(wide)
|
|
||||||
|
|
||||||
fun UByteArray.encodeToBase64Url(): String = toByteArray().encodeToBase64Url()
|
|
@ -1,9 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
class AtomicCounter(initialValue: Long = 0) {
|
|
||||||
private val op = ProtectedOp()
|
|
||||||
var value: Long = initialValue
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun incrementAndGet(): Long = op { ++value }
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Multiplatform interface to perform a regular (not suspend) operation
|
|
||||||
* protected by a platform mutex (where necessary). Get real implementation
|
|
||||||
* with [ProtectedOp]
|
|
||||||
*/
|
|
||||||
interface ProtectedOpImplementation {
|
|
||||||
/**
|
|
||||||
* Call [f] iin mutually exclusive mode, it means that only one invocation
|
|
||||||
* can be active at a time, all the rest are waiting until the current operation
|
|
||||||
* will finish.
|
|
||||||
*/
|
|
||||||
operator fun <T>invoke(f: ()->T): T
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the platform-depended implementation of a mutex-protected operation.
|
|
||||||
*/
|
|
||||||
expect fun ProtectedOp(): ProtectedOpImplementation
|
|
@ -1,20 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
/**
|
|
||||||
* suspend until the flow produces the value to which the
|
|
||||||
* predicate returns true
|
|
||||||
*/
|
|
||||||
suspend fun <T>Flow<T>.waitFor(predicate: (T)->Boolean) {
|
|
||||||
coroutineScope {
|
|
||||||
launch {
|
|
||||||
collect {
|
|
||||||
if( predicate(it) ) cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package net.sergeych.utools
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scan the collection and return the first non-null result of the [predicate] on it.
|
|
||||||
* If all the elements give null with predicate call, returns null.
|
|
||||||
*
|
|
||||||
* Note that collection is scanned only to the first non-null predicate result.
|
|
||||||
*/
|
|
||||||
fun <T,R>Collection<T>.firstNonNull(predicate: (T)->R?): R? {
|
|
||||||
for( x in this ) predicate(x)?.let { return it }
|
|
||||||
return null
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
package net.sergeych.utools
|
|
||||||
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.serializer
|
|
||||||
import net.sergeych.bintools.toDataSource
|
|
||||||
import net.sergeych.bipack.BipackDecoder
|
|
||||||
import net.sergeych.bipack.BipackEncoder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Effectively pack anyk nullable object. The result could be effectively packed
|
|
||||||
* in turn as a part of a more complex structure.
|
|
||||||
*
|
|
||||||
* To avoid packing non-null mark,
|
|
||||||
* we use a zero-size array, which, if in turn encoded, packs into a single
|
|
||||||
* zero byte. Thus, we avoid extra byte spending for unnecessary null
|
|
||||||
* check.
|
|
||||||
*/
|
|
||||||
inline fun <reified T> pack(element: T?): UByteArray = pack(serializer<T>(), element)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpack nullable data packed with [pack]
|
|
||||||
*/
|
|
||||||
inline fun <reified T: Any?> unpack(encoded: UByteArray): T =
|
|
||||||
unpack(serializer<T>(), encoded)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Effectively pack anyk nullable object. The result could be effectively packed
|
|
||||||
* in turn as a part of a more complex structure.
|
|
||||||
*
|
|
||||||
* To avoid packing non-null mark,
|
|
||||||
* we use a zero-size array, which, if in turn encoded, packs into a single
|
|
||||||
* zero byte. Thus, we avoid extra byte spending for unnecessary null
|
|
||||||
* check.
|
|
||||||
*/
|
|
||||||
fun <T>pack(serializer: KSerializer<T>, element: T?): UByteArray =
|
|
||||||
if (element == null) ubyteArrayOf()
|
|
||||||
else BipackEncoder.encode(serializer,element).toUByteArray()
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unpack nullable data packed with [pack]
|
|
||||||
*/
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T: Any?> unpack(serializer: KSerializer<T>, encoded: UByteArray): T =
|
|
||||||
if (encoded.isEmpty()) null as T
|
|
||||||
else BipackDecoder.decode(encoded.toByteArray().toDataSource(),serializer)
|
|
@ -1,12 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package net.sergeych.utools
|
|
||||||
|
|
||||||
import kotlinx.datetime.Clock
|
|
||||||
import kotlinx.datetime.Instant
|
|
||||||
|
|
||||||
fun now(): Instant = Clock.System.now()
|
|
||||||
fun nowToSeconds(): Instant = Clock.System.now().truncateToSeconds()
|
|
||||||
|
|
||||||
fun Instant.truncateToSeconds(): Instant =
|
|
||||||
Instant.fromEpochSeconds(toEpochMilliseconds()/1000)
|
|
@ -1,183 +0,0 @@
|
|||||||
package org.komputing.khash.keccak
|
|
||||||
|
|
||||||
import com.ionspin.kotlin.bignum.integer.BigInteger
|
|
||||||
import org.komputing.khash.keccak.extensions.fillWith
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
object Keccak {
|
|
||||||
|
|
||||||
private val BIT_65 = BigInteger.ONE shl (64)
|
|
||||||
private val MAX_64_BITS = BIT_65 - BigInteger.ONE
|
|
||||||
|
|
||||||
fun digest(value: ByteArray, parameter: KeccakParameter): ByteArray {
|
|
||||||
val uState = IntArray(200)
|
|
||||||
val uMessage = convertToUInt(value)
|
|
||||||
|
|
||||||
var blockSize = 0
|
|
||||||
var inputOffset = 0
|
|
||||||
|
|
||||||
// Absorbing phase
|
|
||||||
while (inputOffset < uMessage.size) {
|
|
||||||
blockSize = min(uMessage.size - inputOffset, parameter.rateInBytes)
|
|
||||||
for (i in 0 until blockSize) {
|
|
||||||
uState[i] = uState[i] xor uMessage[i + inputOffset]
|
|
||||||
}
|
|
||||||
|
|
||||||
inputOffset += blockSize
|
|
||||||
|
|
||||||
if (blockSize == parameter.rateInBytes) {
|
|
||||||
doF(uState)
|
|
||||||
blockSize = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padding phase
|
|
||||||
uState[blockSize] = uState[blockSize] xor parameter.d
|
|
||||||
if (parameter.d and 0x80 != 0 && blockSize == parameter.rateInBytes - 1) {
|
|
||||||
doF(uState)
|
|
||||||
}
|
|
||||||
|
|
||||||
uState[parameter.rateInBytes - 1] = uState[parameter.rateInBytes - 1] xor 0x80
|
|
||||||
doF(uState)
|
|
||||||
|
|
||||||
// Squeezing phase
|
|
||||||
val byteResults = mutableListOf<Byte>()
|
|
||||||
var tOutputLen = parameter.outputLengthInBytes
|
|
||||||
while (tOutputLen > 0) {
|
|
||||||
blockSize = min(tOutputLen, parameter.rateInBytes)
|
|
||||||
for (i in 0 until blockSize) {
|
|
||||||
byteResults.add(uState[i].toByte().toInt().toByte())
|
|
||||||
}
|
|
||||||
|
|
||||||
tOutputLen -= blockSize
|
|
||||||
if (tOutputLen > 0) {
|
|
||||||
doF(uState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return byteResults.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doF(uState: IntArray) {
|
|
||||||
val lState = Array(5) { Array(5) { BigInteger.ZERO } }
|
|
||||||
|
|
||||||
for (i in 0..4) {
|
|
||||||
for (j in 0..4) {
|
|
||||||
val data = IntArray(8)
|
|
||||||
val index = 8 * (i + 5 * j)
|
|
||||||
uState.copyInto(data, 0, index, index + data.size)
|
|
||||||
lState[i][j] = convertFromLittleEndianTo64(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
roundB(lState)
|
|
||||||
|
|
||||||
uState.fillWith(0)
|
|
||||||
for (i in 0..4) {
|
|
||||||
for (j in 0..4) {
|
|
||||||
val data = convertFrom64ToLittleEndian(lState[i][j])
|
|
||||||
data.copyInto(uState, 8 * (i + 5 * j))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Permutation on the given state.
|
|
||||||
*/
|
|
||||||
private fun roundB(state: Array<Array<BigInteger>>) {
|
|
||||||
var lfsrState = 1
|
|
||||||
for (round in 0..23) {
|
|
||||||
val c = arrayOfNulls<BigInteger>(5)
|
|
||||||
val d = arrayOfNulls<BigInteger>(5)
|
|
||||||
|
|
||||||
// θ step
|
|
||||||
for (i in 0..4) {
|
|
||||||
c[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4])
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0..4) {
|
|
||||||
d[i] = c[(i + 4) % 5]!!.xor(c[(i + 1) % 5]!!.leftRotate64(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0..4) {
|
|
||||||
for (j in 0..4) {
|
|
||||||
state[i][j] = state[i][j].xor(d[i]!!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ρ and π steps
|
|
||||||
var x = 1
|
|
||||||
var y = 0
|
|
||||||
var current = state[x][y]
|
|
||||||
for (i in 0..23) {
|
|
||||||
val tX = x
|
|
||||||
x = y
|
|
||||||
y = (2 * tX + 3 * y) % 5
|
|
||||||
|
|
||||||
val shiftValue = current
|
|
||||||
current = state[x][y]
|
|
||||||
|
|
||||||
state[x][y] = shiftValue.leftRotate64Safely((i + 1) * (i + 2) / 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// χ step
|
|
||||||
for (j in 0..4) {
|
|
||||||
val t = arrayOfNulls<BigInteger>(5)
|
|
||||||
for (i in 0..4) {
|
|
||||||
t[i] = state[i][j]
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0..4) {
|
|
||||||
// ~t[(i + 1) % 5]
|
|
||||||
val invertVal = t[(i + 1) % 5]!!.xor(MAX_64_BITS)
|
|
||||||
// t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5])
|
|
||||||
state[i][j] = t[i]!!.xor(invertVal.and(t[(i + 2) % 5]!!))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ι step
|
|
||||||
for (i in 0..6) {
|
|
||||||
lfsrState = (lfsrState shl 1 xor (lfsrState shr 7) * 0x71) % 256
|
|
||||||
// pow(2, i) - 1
|
|
||||||
val bitPosition = (1 shl i) - 1
|
|
||||||
if (lfsrState and 2 != 0) {
|
|
||||||
state[0][0] = state[0][0].xor(BigInteger.ONE shl bitPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given [data] array to an [IntArray] containing UInt values.
|
|
||||||
*/
|
|
||||||
private fun convertToUInt(data: ByteArray) = IntArray(data.size) {
|
|
||||||
data[it].toInt() and 0xFF
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given [data] array containing the little endian representation of a number to a [BigInteger].
|
|
||||||
*/
|
|
||||||
private fun convertFromLittleEndianTo64(data: IntArray): BigInteger {
|
|
||||||
val value = data.map { it.toString(16) }
|
|
||||||
.map { if (it.length == 2) it else "0$it" }
|
|
||||||
.reversed()
|
|
||||||
.joinToString("")
|
|
||||||
return BigInteger.parseString(value, 16)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given [BigInteger] to a little endian representation as an [IntArray].
|
|
||||||
*/
|
|
||||||
private fun convertFrom64ToLittleEndian(uLong: BigInteger): IntArray {
|
|
||||||
val asHex = uLong.toString(16)
|
|
||||||
val asHexPadded = "0".repeat((8 * 2) - asHex.length) + asHex
|
|
||||||
return IntArray(8) {
|
|
||||||
((7 - it) * 2).let { pos ->
|
|
||||||
asHexPadded.substring(pos, pos + 2).toInt(16)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BigInteger.leftRotate64Safely(rotate: Int) = leftRotate64(rotate % 64)
|
|
||||||
|
|
||||||
private fun BigInteger.leftRotate64(rotate: Int) = (this shr (64 - rotate)).add(this shl rotate).mod(BIT_65)
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
|
|
||||||
package org.komputing.khash.keccak
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters defining the FIPS 202 standard.
|
|
||||||
*/
|
|
||||||
enum class KeccakParameter(val rateInBytes: Int,val outputLengthInBytes: Int, val d: Int) {
|
|
||||||
|
|
||||||
KECCAK_224(144, 28, 0x01),
|
|
||||||
KECCAK_256(136, 32, 0x01),
|
|
||||||
KECCAK_384(104, 48, 0x01),
|
|
||||||
KECCAK_512(72, 64, 0x01),
|
|
||||||
|
|
||||||
SHA3_224(144, 28, 0x06),
|
|
||||||
SHA3_256(136, 32, 0x06),
|
|
||||||
SHA3_384(104, 48, 0x06),
|
|
||||||
SHA3_512(72, 64, 0x06),
|
|
||||||
|
|
||||||
SHAKE128(168, 32, 0x1F),
|
|
||||||
SHAKE256(136, 64, 0x1F)
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package org.komputing.khash.keccak.extensions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns the specified int value to each element of the specified
|
|
||||||
* range in the specified array of ints. The range to be filled
|
|
||||||
* extends from index <tt>fromIndex</tt>, inclusive, to index
|
|
||||||
* <tt>toIndex</tt>, exclusive. (If <tt>fromIndex==toIndex</tt>, the
|
|
||||||
* range to be filled is empty.)
|
|
||||||
*
|
|
||||||
* @param fromIndex the index of the first element (inclusive) to be
|
|
||||||
* filled with the specified value
|
|
||||||
* @param toIndex the index of the last element (exclusive) to be
|
|
||||||
* filled with the specified value
|
|
||||||
* @param value the value to be stored in all elements of the array
|
|
||||||
* @throws IllegalArgumentException if <tt>fromIndex > toIndex</tt>
|
|
||||||
* @throws ArrayIndexOutOfBoundsException if <tt>fromIndex < 0</tt> or
|
|
||||||
* <tt>toIndex > a.length</tt>
|
|
||||||
*/
|
|
||||||
internal fun IntArray.fillWith(value: Int, fromIndex: Int = 0, toIndex: Int = this.size) {
|
|
||||||
if (fromIndex > toIndex) {
|
|
||||||
throw IllegalArgumentException(
|
|
||||||
"fromIndex($fromIndex) > toIndex($toIndex)"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromIndex < 0) {
|
|
||||||
throw ArrayIndexOutOfBoundsException(fromIndex)
|
|
||||||
}
|
|
||||||
if (toIndex > this.size) {
|
|
||||||
throw ArrayIndexOutOfBoundsException(toIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in fromIndex until toIndex)
|
|
||||||
this[i] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new [ArrayIndexOutOfBoundsException]
|
|
||||||
* class with an argument indicating the illegal index.
|
|
||||||
* @param index the illegal index.
|
|
||||||
*/
|
|
||||||
internal class ArrayIndexOutOfBoundsException(index: Int) : Throwable("Array index out of range: $index")
|
|
@ -1,19 +0,0 @@
|
|||||||
@file:Suppress("unused")
|
|
||||||
package org.komputing.khash.keccak.extensions
|
|
||||||
|
|
||||||
import org.komputing.khash.keccak.Keccak
|
|
||||||
import org.komputing.khash.keccak.KeccakParameter
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the proper Keccak digest of [this] byte array based on the given [parameter]
|
|
||||||
*/
|
|
||||||
fun ByteArray.digestKeccak(parameter: KeccakParameter): ByteArray {
|
|
||||||
return Keccak.digest(this, parameter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the proper Keccak digest of [this] string based on the given [parameter]
|
|
||||||
*/
|
|
||||||
fun String.digestKeccak(parameter: KeccakParameter): ByteArray {
|
|
||||||
return Keccak.digest(encodeToByteArray(), parameter)
|
|
||||||
}
|
|
@ -11,7 +11,7 @@ class KeysTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testCreationAndMap() = runTest {
|
fun testCreationAndMap() = runTest {
|
||||||
initCrypto()
|
initCrypto()
|
||||||
val (stk,pbk) = SigningKey.Secret.pair()
|
val (stk,pbk) = SigningKey.pair()
|
||||||
|
|
||||||
val x = mapOf( stk to "STK!", pbk to "PBK!")
|
val x = mapOf( stk to "STK!", pbk to "PBK!")
|
||||||
assertEquals("STK!", x[stk])
|
assertEquals("STK!", x[stk])
|
||||||
@ -22,22 +22,22 @@ class KeysTest {
|
|||||||
|
|
||||||
val data = "8 rays dev!".encodeToUByteArray()
|
val data = "8 rays dev!".encodeToUByteArray()
|
||||||
val data1 = "8 rays dev!".encodeToUByteArray()
|
val data1 = "8 rays dev!".encodeToUByteArray()
|
||||||
val s = SignedBox.Seal.create(stk, data)
|
val s = stk.seal(data)
|
||||||
assertTrue(s.verify(data))
|
assertTrue(s.verify(data))
|
||||||
|
|
||||||
data1[0] = 0x01u
|
data1[0] = 0x01u
|
||||||
assertFalse(s.verify(data1))
|
assertFalse(s.verify(data1))
|
||||||
val p2 = SigningKey.Secret.pair()
|
val p2 = SigningKey.pair()
|
||||||
val p3 = SigningKey.Secret.pair()
|
val p3 = SigningKey.pair()
|
||||||
|
|
||||||
val ms = SignedBox(data, s1) + p2.signing
|
val ms = SignedBox(data, s1) + p2.secretKey
|
||||||
|
|
||||||
// non tampered:
|
// non tampered:
|
||||||
val ms1 = unpack<SignedBox>(pack(ms))
|
val ms1 = unpack<SignedBox>(pack(ms))
|
||||||
assertContentEquals(data, ms1.message)
|
assertContentEquals(data, ms1.message)
|
||||||
assertTrue(pbk in ms1)
|
assertTrue(pbk in ms1)
|
||||||
assertTrue(p2.aPublic in ms1)
|
assertTrue(p2.publicKey in ms1)
|
||||||
assertTrue(p3.aPublic !in ms1)
|
assertTrue(p3.publicKey !in ms1)
|
||||||
|
|
||||||
assertThrows<IllegalSignatureException> {
|
assertThrows<IllegalSignatureException> {
|
||||||
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
|
unpack<SignedBox>(pack(ms).also { it[3] = 1u })
|
||||||
|
@ -171,8 +171,8 @@ class TransportTest {
|
|||||||
// Log.defaultLevel = Log.Level.DEBUG
|
// Log.defaultLevel = Log.Level.DEBUG
|
||||||
val (d1, d2) = createTestDevice()
|
val (d1, d2) = createTestDevice()
|
||||||
|
|
||||||
val serverId = SigningKey.Secret.pair()
|
val serverId = SigningKey.pair()
|
||||||
val clientId = SigningKey.Secret.pair()
|
val clientId = SigningKey.pair()
|
||||||
|
|
||||||
val serverInterface = KiloInterface<String>().apply {
|
val serverInterface = KiloInterface<String>().apply {
|
||||||
on(cmdPing) {
|
on(cmdPing) {
|
||||||
@ -193,13 +193,13 @@ class TransportTest {
|
|||||||
registerError { IllegalStateException() }
|
registerError { IllegalStateException() }
|
||||||
registerError { IllegalArgumentException(it) }
|
registerError { IllegalArgumentException(it) }
|
||||||
}
|
}
|
||||||
val kiloServerConnection = KiloServerConnection(serverInterface, d1, "server session", serverId.signing)
|
val kiloServerConnection = KiloServerConnection(serverInterface, d1, "server session", serverId.secretKey)
|
||||||
launch { kiloServerConnection.run() }
|
launch { kiloServerConnection.run() }
|
||||||
|
|
||||||
var cnt = 0
|
var cnt = 0
|
||||||
val client = KiloClient {
|
val client = KiloClient {
|
||||||
session { "client session!" }
|
session { "client session!" }
|
||||||
secretIdKey = clientId.signing
|
secretIdKey = clientId.secretKey
|
||||||
local {
|
local {
|
||||||
on(cmdPush) {
|
on(cmdPush) {
|
||||||
"server push: $it"
|
"server push: $it"
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
|
|
||||||
// JS targets are inherently single-threaded, so we do noting:
|
|
||||||
override fun <T> invoke(f: () -> T): T = f()
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
|
|
||||||
private val lock = Object()
|
|
||||||
override fun <T> invoke(f: () -> T): T {
|
|
||||||
synchronized(lock) { return f() }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package net.sergeych.tools
|
|
||||||
|
|
||||||
import kotlinx.atomicfu.locks.SynchronizedObject
|
|
||||||
import kotlinx.atomicfu.locks.synchronized
|
|
||||||
|
|
||||||
actual fun ProtectedOp(): ProtectedOpImplementation = object : ProtectedOpImplementation {
|
|
||||||
private val lock = SynchronizedObject()
|
|
||||||
override fun <T> invoke(f: () -> T): T {
|
|
||||||
synchronized(lock) {
|
|
||||||
return f()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user