missing initial files + publishing
This commit is contained in:
		
							parent
							
								
									aaa8c436b0
								
							
						
					
					
						commit
						f429cfe418
					
				@ -87,3 +87,21 @@ kotlin {
 | 
			
		||||
        val nativeTest by getting
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
publishing {
 | 
			
		||||
    val mavenToken by lazy {
 | 
			
		||||
        File("${System.getProperty("user.home")}/.gitea_token").readText()
 | 
			
		||||
    }
 | 
			
		||||
    repositories {
 | 
			
		||||
        maven {
 | 
			
		||||
            credentials(HttpHeaderCredentials::class) {
 | 
			
		||||
                name = "Authorization"
 | 
			
		||||
                value = mavenToken
 | 
			
		||||
            }
 | 
			
		||||
            url = uri("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
 | 
			
		||||
            authentication {
 | 
			
		||||
                create("Authorization", HttpHeaderAuthentication::class)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										27
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/InitCrypto.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/InitCrypto.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
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
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/Seal.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SignedBox.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SignedBox.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SigningKey.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/SigningKey.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
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")
 | 
			
		||||
							
								
								
									
										7
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/contrail.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/contrail.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										111
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/tools.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/tools.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,111 @@
 | 
			
		||||
@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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/utools.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/commonMain/kotlin/net/sergeych/crypto2/utools.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
@file:Suppress("unused")
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
							
								
								
									
										10
									
								
								src/commonMain/kotlin/net/sergeych/tools/AtomicCounter.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/commonMain/kotlin/net/sergeych/tools/AtomicCounter.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
package net.sergeych.tools
 | 
			
		||||
 | 
			
		||||
@Suppress("unused")
 | 
			
		||||
class AtomicCounter(initialValue: Long = 0) {
 | 
			
		||||
 private val op = ProtectedOp()
 | 
			
		||||
    var value: Long = initialValue
 | 
			
		||||
        private set
 | 
			
		||||
 | 
			
		||||
    fun incrementAndGet(): Long = op { ++value }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/commonMain/kotlin/net/sergeych/tools/ProtectedOp.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/commonMain/kotlin/net/sergeych/tools/ProtectedOp.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										22
									
								
								src/commonMain/kotlin/net/sergeych/tools/flow_tools.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/commonMain/kotlin/net/sergeych/tools/flow_tools.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
@file:Suppress("unused")
 | 
			
		||||
 | 
			
		||||
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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/commonMain/kotlin/net/sergeych/utools/collections.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/commonMain/kotlin/net/sergeych/utools/collections.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
			
		||||
@file:Suppress("unused")
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								src/commonMain/kotlin/net/sergeych/utools/packing.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/commonMain/kotlin/net/sergeych/utools/packing.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
			
		||||
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)
 | 
			
		||||
							
								
								
									
										12
									
								
								src/commonMain/kotlin/net/sergeych/utools/time.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/commonMain/kotlin/net/sergeych/utools/time.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
@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)
 | 
			
		||||
							
								
								
									
										183
									
								
								src/commonMain/kotlin/org/komputing/khash/keccak/Keccak.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/commonMain/kotlin/org/komputing/khash/keccak/Keccak.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,22 @@
 | 
			
		||||
@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)
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,42 @@
 | 
			
		||||
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")
 | 
			
		||||
@ -0,0 +1,19 @@
 | 
			
		||||
@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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								src/commonTest/kotlin/KeysTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/commonTest/kotlin/KeysTest.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
import com.ionspin.kotlin.crypto.secretbox.SecretBox
 | 
			
		||||
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
 | 
			
		||||
import com.ionspin.kotlin.crypto.util.encodeToUByteArray
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.crypto2.*
 | 
			
		||||
import net.sergeych.utools.pack
 | 
			
		||||
import net.sergeych.utools.unpack
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
class KeysTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testCreationAndMap() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val (stk,pbk) = SigningKey.Secret.pair()
 | 
			
		||||
 | 
			
		||||
        val x = mapOf( stk to "STK!", pbk to "PBK!")
 | 
			
		||||
        assertEquals("STK!", x[stk])
 | 
			
		||||
        val s1 = SigningKey.Secret(stk.packed)
 | 
			
		||||
        assertEquals(stk, s1)
 | 
			
		||||
        assertEquals("STK!", x[s1])
 | 
			
		||||
        assertEquals("PBK!", x[pbk])
 | 
			
		||||
 | 
			
		||||
        val data = "8 rays dev!".encodeToUByteArray()
 | 
			
		||||
        val data1 = "8 rays dev!".encodeToUByteArray()
 | 
			
		||||
        val s = stk.seal(data)
 | 
			
		||||
        assertTrue(s.verify(data))
 | 
			
		||||
 | 
			
		||||
        data1[0] = 0x01u
 | 
			
		||||
        assertFalse(s.verify(data1))
 | 
			
		||||
        val p2 = SigningKey.Secret.pair()
 | 
			
		||||
        val p3 = SigningKey.Secret.pair()
 | 
			
		||||
 | 
			
		||||
        val ms = SignedBox(data, s1) + p2.signing
 | 
			
		||||
 | 
			
		||||
        // non tampered:
 | 
			
		||||
        val ms1 = unpack<SignedBox>(pack(ms))
 | 
			
		||||
        assertContentEquals(data, ms1.message)
 | 
			
		||||
        assertTrue(pbk in ms1)
 | 
			
		||||
        assertTrue(p2.aPublic in ms1)
 | 
			
		||||
        assertTrue(p3.aPublic !in ms1)
 | 
			
		||||
 | 
			
		||||
        assertThrows<IllegalSignatureException> {
 | 
			
		||||
            unpack<SignedBox>(pack(ms).also { it[3] = 1u })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun secretEncryptTest() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val key = SecretBox.keygen()
 | 
			
		||||
        val key1 = SecretBox.keygen()
 | 
			
		||||
        assertEquals("hello", decrypt(key, encrypt(key, "hello".encodeToUByteArray())).decodeFromUByteArray())
 | 
			
		||||
        assertThrows<DecryptionFailedException> {
 | 
			
		||||
            decrypt(key, encrypt(key1, "hello".encodeToUByteArray())).decodeFromUByteArray()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/commonTest/kotlin/PackTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/commonTest/kotlin/PackTest.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import kotlinx.datetime.Instant
 | 
			
		||||
import net.sergeych.crypto2.initCrypto
 | 
			
		||||
import net.sergeych.utools.nowToSeconds
 | 
			
		||||
import net.sergeych.utools.pack
 | 
			
		||||
import net.sergeych.utools.unpack
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.time.Duration.Companion.microseconds
 | 
			
		||||
 | 
			
		||||
class PackTest {
 | 
			
		||||
        inline fun <reified T>check(x: T?) {
 | 
			
		||||
            assertEquals(x, unpack<T>(pack(x)))
 | 
			
		||||
        }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testNullPack() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val d = pack("Hello")
 | 
			
		||||
        assertEquals(6, d.size)
 | 
			
		||||
        check(1)
 | 
			
		||||
        check(2L)
 | 
			
		||||
        check(1.00)
 | 
			
		||||
        check("hello")
 | 
			
		||||
        check<String>(null)
 | 
			
		||||
        check<Long?>(null)
 | 
			
		||||
    }
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testTimePack() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val t1 = nowToSeconds()
 | 
			
		||||
        val t2 = t1 + 1.microseconds
 | 
			
		||||
        assertEquals(t1, unpack<Instant>(pack(t2)))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/commonTest/kotlin/ToolsTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/commonTest/kotlin/ToolsTest.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.crypto2.createContrail
 | 
			
		||||
import net.sergeych.crypto2.initCrypto
 | 
			
		||||
import net.sergeych.crypto2.isValidContrail
 | 
			
		||||
import kotlin.test.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertFalse
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
class ToolsTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testContrails() = runTest {
 | 
			
		||||
        initCrypto()
 | 
			
		||||
        val c = createContrail(ubyteArrayOf(1u, 2u, 3u, 4u, 5u))
 | 
			
		||||
        assertEquals(134u, c[0])
 | 
			
		||||
        assertTrue { isValidContrail(c) }
 | 
			
		||||
        c[2] = 11u
 | 
			
		||||
        assertFalse { isValidContrail(c) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								src/commonTest/kotlin/assertThrows.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/commonTest/kotlin/assertThrows.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
import kotlin.test.fail
 | 
			
		||||
 | 
			
		||||
inline fun <reified T: Throwable>assertThrows(f: ()->Unit): T {
 | 
			
		||||
    val name = T::class.simpleName
 | 
			
		||||
    try {
 | 
			
		||||
        f()
 | 
			
		||||
        fail("expected to throw $name but threw nothing")
 | 
			
		||||
    }
 | 
			
		||||
    catch(x: Throwable) {
 | 
			
		||||
        if( x is T ) return x
 | 
			
		||||
        fail("expected to throw $name but instead threw ${x::class.simpleName}: $x")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/jsMain/kotlin/net/sergeych/tools/ProtectedOp.js.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/jsMain/kotlin/net/sergeych/tools/ProtectedOp.js.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
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()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								src/jvmMain/kotlin/net/sergeych/tools/ProtectedOp.jvm.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/jvmMain/kotlin/net/sergeych/tools/ProtectedOp.jvm.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
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() }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
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