Compare commits

..

19 Commits

Author SHA1 Message Date
d180da309b added copyright, license mention and other #10x2linus stuff. 2025-02-20 12:21:42 +03:00
c9e3c57ee2 0.8.1 started: experimental support of the wasmJS target. 2025-02-13 10:04:16 +03:00
81e02ac88e Merge pull request 'Fixing multiplatform-crypto-libsodium-bindings dependencies' (#8) from YoungBlood/crypto2:master into master
Reviewed-on: #8
2025-02-13 09:54:58 +03:00
kildishevps
e8d6b2fc02 Fixing multiplatform-crypto-libsodium-bindings dependencies 2025-02-09 21:59:28 +03:00
277dc62553 Merge pull request 'Add wasmJs target' (#7) from YoungBlood/crypto2:master into master
Reviewed-on: #7
2025-02-09 16:57:38 +03:00
7e52a72c6a Add wasmJs target 2025-02-05 16:39:10 +03:00
7fa3ab1ca8 seal now allows up to 45s in future to tolerate some time difference 2025-02-03 17:13:28 +03:00
242cc7d0f5 more tests 2024-11-29 15:42:54 +07:00
db7453fbb2 +id now has public constructor
+default bas64 repr for ByteChunk
2024-11-29 11:25:01 +07:00
cef7e4abed add general support from encoding to string and restoring from string various keys formats. + docs. 2024-11-27 18:11:45 +07:00
e8fa634640 0.7.1 started. fixed secret key serialization excessive size for signing/verifying keys 2024-11-26 18:50:07 +07:00
10ec58ec08 +hash salt derivation for any size. better naming 2024-11-25 17:11:08 +07:00
9f7babdf58 extending to be more convenient 2024-11-21 15:12:24 +07:00
640ceb448e fix #1 UniversalPrivateKey & UniversalPublicKey 2024-11-13 18:24:44 +07:00
194fe22afa hash StreamProcessor is now public
better docs on BinaryId
some sugar
2024-09-28 00:35:07 +03:00
8eed7a3de7 v0.5.8: Multikeys 2024-09-22 23:04:55 +03:00
4cbc17334c +Multikey.AnyKey 2024-09-22 22:54:31 +03:00
1191de284e 0.5.8-SNAPSHOT: Multikeys 2024-09-15 12:29:04 +03:00
8e652e0421 0.5.8-SNAPSHOT: Multikeys 2024-09-12 18:10:41 +03:00
65 changed files with 1695 additions and 58 deletions

1
.gitignore vendored
View File

@ -41,4 +41,5 @@ out/
# Other # Other
.kotlin .kotlin
.idea .idea
.gigaide
/kotlin-js-store/yarn.lock /kotlin-js-store/yarn.lock

View File

@ -8,6 +8,8 @@ All primitives meant to send over the network or store are `kotlinx.serializatio
# Important notes on upgrade # Important notes on upgrade
___Please upgrade to 0.7.1+___ as it has much more compact but not backward-compatible serialization format!
Since version __0.5.*__ key identity calculation for asymmetric keys is updated Since version __0.5.*__ key identity calculation for asymmetric keys is updated
to make it safer for theoretic future attack on blake2b hashing. Key.id values to make it safer for theoretic future attack on blake2b hashing. Key.id values
are incompatible with older. Sorry for inconvenience. are incompatible with older. Sorry for inconvenience.
@ -19,7 +21,7 @@ repositories {
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven") maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
} }
dependencies { dependencies {
import("net.sergeych:crypto2:0.5.7") import("net.sergeych:crypto2:0.7.1-SNAPSHOT")
} }
``` ```
@ -34,6 +36,10 @@ Please see the current documentation [here](https://code.sergeych.net/docs/crypt
- All moder browsers, including mobile - All moder browsers, including mobile
- Node.js - Node.js
## WasmJs
- All moder browsers, including mobile
## JVM ## JVM
- Android - Android
@ -108,4 +114,13 @@ Secret key encryption and signing/verifying uses Edwards curves 25519 algorithms
- SHA3 256, 384, more are on the way. - SHA3 256, 384, more are on the way.
- CRC-protected binary ID with magic numbers to implement human-friendly IDS with type checks - CRC-protected binary ID with magic numbers to implement human-friendly IDS with type checks
## Licensing
# Licensing
This is work in progress, not yet moved to public domain;
you need to obtain a license from https://8-rays.dev or [Sergey Chernov]. For open source projects it will most be free on some special terms.
It will be moved to open source; we also guarantee that it will be moved to open source immediately if the software export restrictions will be lifted. We do not support such practices here at 8-rays.dev and assume open source must be open.
[Sergey Chernov]: https://t.me/real_sergeych

View File

@ -1,4 +1,14 @@
#!/bin/bash #!/bin/bash
#
# Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
#
# You may use, distribute and modify this code under the
# terms of the private license, which you must obtain from the author
#
# To obtain the license, contact the author: https://t.me/real_sergeych or email to
# real dot sergeych at gmail.
#
set -e set -e
./gradlew dokkaHtml ./gradlew dokkaHtml
rsync -avz ./build/dokka/* code.sergeych.net:/bigstore/sergeych_pub/code/docs/crypto2 rsync -avz ./build/dokka/* code.sergeych.net:/bigstore/sergeych_pub/code/docs/crypto2

View File

@ -1,3 +1,15 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
@ -8,17 +20,19 @@ plugins {
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.5.7" version = "0.8.1"
repositories { repositories {
mavenCentral() mavenCentral()
maven("https://maven.universablockchain.com/") maven("https://maven.universablockchain.com/")
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven") maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
maven("https://gitea.sergeych.net/api/packages/YoungBlood/maven")
mavenLocal() mavenLocal()
} }
kotlin { kotlin {
jvm { jvm {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions { compilerOptions {
jvmTarget = JvmTarget.JVM_11 jvmTarget = JvmTarget.JVM_11
} }
@ -36,9 +50,11 @@ kotlin {
iosArm64() iosArm64()
iosSimulatorArm64() iosSimulatorArm64()
mingwX64() mingwX64()
// @OptIn(ExperimentalWasmDsl::class) @OptIn(ExperimentalWasmDsl::class)
// wasmJs() //no libsodium bindings yet (strangely) wasmJs {
// val ktor_version = "2.3.6" browser()
}
val ktor_version = "2.3.6"
sourceSets { sourceSets {
all { all {
@ -78,8 +94,21 @@ kotlin {
} }
} }
val jvmTest by getting val jvmTest by getting
for (platform in listOf(linuxMain, macosMain, iosMain, mingwMain)) for (platform in listOf(linuxX64Main, linuxArm64Main, macosX64Main, macosArm64Main, iosX64Main, iosArm64Main, iosSimulatorArm64Main, mingwX64Main))
platform { dependsOn(native) } platform { dependsOn(native) }
val wasmJsMain by getting {
val wasmJsTargetRegex = Regex(pattern = "wasmJs.*")
configurations.all {
if (wasmJsTargetRegex.containsMatchIn(input = this.name)) {
resolutionStrategy.dependencySubstitution {
substitute(module("com.ionspin.kotlin:multiplatform-crypto-libsodium-bindings:0.9.2"))
.using(module("net.sergeych:multiplatform-crypto-libsodium-bindings:0.9.4-SNAPSHOT"))
.withoutClassifier()
}
}
}
}
} }
} }

View File

@ -1 +1,11 @@
#
# Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
#
# You may use, distribute and modify this code under the
# terms of the private license, which you must obtain from the author
#
# To obtain the license, contact the author: https://t.me/real_sergeych or email to
# real dot sergeych at gmail.
#
kotlin.code.style=official kotlin.code.style=official

View File

@ -1,3 +1,13 @@
#
# Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
#
# You may use, distribute and modify this code under the
# terms of the private license, which you must obtain from the author
#
# To obtain the license, contact the author: https://t.me/real_sergeych or email to
# real dot sergeych at gmail.
#
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip

16
gradlew vendored
View File

@ -1,19 +1,13 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # You may use, distribute and modify this code under the
# you may not use this file except in compliance with the License. # terms of the private license, which you must obtain from the author
# You may obtain a copy of the License at
# #
# https://www.apache.org/licenses/LICENSE-2.0 # To obtain the license, contact the author: https://t.me/real_sergeych or email to
# # real dot sergeych at gmail.
# 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.
# #
############################################################################## ##############################################################################

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
pluginManagement { pluginManagement {
repositories { repositories {
mavenCentral() mavenCentral()

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.box.Box import com.ionspin.kotlin.crypto.box.Box

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray
@ -5,17 +15,60 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import net.sergeych.bintools.CRC import net.sergeych.bintools.CRC
import net.sergeych.bintools.CRC8 import net.sergeych.bintools.CRC8
import net.sergeych.crypto2.BinaryId.Companion.createFromBytes
import net.sergeych.crypto2.BinaryId.Companion.createFromString
import net.sergeych.crypto2.BinaryId.Companion.createFromUBytes
import net.sergeych.crypto2.BinaryId.Companion.createRandom
import net.sergeych.crypto2.BinaryId.IncomparableException
import net.sergeych.crypto2.BinaryId.InvalidException
import net.sergeych.mp_tools.decodeBase64Url import net.sergeych.mp_tools.decodeBase64Url
import kotlin.random.Random import kotlin.random.Random
/**
* Binary identifier with control code and magic number. To create instaance
* use one of [createFromBytes], [createFromString], [createFromUBytes],
* or [createRandom], also deserialize serialized one.
*
* Integrity is checked on instantiating automatically.
*
* It is comparable to other BinaryId as long as both have the same [magic]. Attempt to
* compare these that differ throws [IncomparableException]
*
* ### Internal structure
*
* Say we have a `BinaryId` of size `N` bytes. The inner structure will be:
*
* | offset | meaning |
* |-----------|---------|
* | 0 ..< N-2 | id bytes |
* | N-2 | magic, 0..255 |
* | N-1 | CRC8, polynomial 0xA7, as in Bluetooth |
*
* @throws InvalidException if crc check failed
*/
@Serializable @Serializable
open class BinaryId protected constructor ( open class BinaryId(
/**
* The packed binary id. Note that serialized version is one byte longer containing
* the size prefix
*/
val id: UByteArray, val id: UByteArray,
) : Comparable<BinaryId> { ) : Comparable<BinaryId> {
class InvalidException(text: String) : IllegalArgumentException(text) /**
* Bad format (crc does not match)
*/
class InvalidException(text: String,reason: Throwable?=null) : IllegalArgumentException(text,reason)
/**
* Attempt to compare binary ids with different magic. In this case only [equals]
* works, but [compareTo] throws this exception.
*/
class IncomparableException(text: String) : IllegalArgumentException(text) class IncomparableException(text: String) : IllegalArgumentException(text)
/**
* magic number (as decoded), `0..255`
*/
@Transient @Transient
val magic: Int = run { val magic: Int = run {
if (id.size < 4) throw InvalidException("BinaryId is too short") if (id.size < 4) throw InvalidException("BinaryId is too short")
@ -30,7 +83,9 @@ open class BinaryId protected constructor (
private val innerData: UByteArray by lazy { id.sliceArray( 1..< id.size-1 ) } private val innerData: UByteArray by lazy { id.sliceArray( 1..< id.size-1 ) }
/** /**
* The id body: all the bytes except check and magic. These could carry useful information. * The ID body: all the bytes except check and magic. ID bytes could carry useful information.
*
* - `id.size` is [body] size + 2 (see [BinaryId] inner structure)
*/ */
val body: UByteArray by lazy { id.sliceArray( 0 until id.size-2 ) } val body: UByteArray by lazy { id.sliceArray( 0 until id.size-2 ) }
@ -41,6 +96,11 @@ open class BinaryId protected constructor (
VerifyingPublicKey(body) VerifyingPublicKey(body)
} }
/**
* Try to recnstruct a [PublicKey] from [id] bytes. For such keys, [PublicKey.id] and [SecretKey.id]
* are made from public key bytes so it could be restored from such an ID
*
*/
val asPublicKey: PublicKey by lazy { val asPublicKey: PublicKey by lazy {
if( magic != KeysmagicNumber.defaultAssymmetric.ordinal) if( magic != KeysmagicNumber.defaultAssymmetric.ordinal)
throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}") throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}")
@ -50,6 +110,11 @@ open class BinaryId protected constructor (
override fun toString(): String = id.encodeToBase64Url() override fun toString(): String = id.encodeToBase64Url()
/**
* Compare to another ID which __must have the same [magic]__ number; note that [equals]
* works well despite magic inequity.
* @throws IncomparableException if magic id do not match
*/
override fun compareTo(other: BinaryId): Int { override fun compareTo(other: BinaryId): Int {
if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})") if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})")
val id1 = other.id val id1 = other.id
@ -81,8 +146,9 @@ open class BinaryId protected constructor (
* Restore a string representation of existing BinaryId. * Restore a string representation of existing BinaryId.
*/ */
@Suppress("unused") @Suppress("unused")
fun restoreFromString(str: String): BinaryId = fun restoreFromString(str: String): BinaryId = kotlin.runCatching {
BinaryId(str.decodeBase64Url().toUByteArray()) BinaryId(str.decodeBase64Url().toUByteArray())
}.getOrElse { throw InvalidException("can't parse binary id: $str", it) }
fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray()) fun createFromBytes(magic: Int, bytes: ByteArray): BinaryId = createFromUBytes(magic, bytes.toUByteArray())

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2
import kotlinx.serialization.Serializable
import net.sergeych.bintools.decodeHex
import net.sergeych.bintools.encodeToHex
import kotlin.math.min
/**
* Bytes sequence with comparison, concatenation, and string representation,
* could be used as hash keys for pure binary values, etc.
*/
@Suppress("unused")
@Serializable
class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
val size: Int get() = data.size
/**
* Per-byte comparison also of different length. From two chunks
* of different size but equal beginning, the shorter is considered
* the smaller.
*/
override fun compareTo(other: ByteChunk): Int {
val limit = min(size, other.size)
for( i in 0 ..< limit) {
val own = data[i]
val their = other.data[i]
if( own < their) return -1
else if( own > their) return 1
}
if( size < other.size ) return -1
if( size > other.size ) return 1
return 0
}
/**
* Equal chunks means content equality.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ByteChunk) return false
return data contentEquals other.data
}
/**
* Content-based hash code
*/
override fun hashCode(): Int {
return data.contentHashCode()
}
/**
* hex representation of data
*/
override fun toString(): String = base64Url
/**
* Hex encoded data
*/
val hex by lazy { data.encodeToHex() }
val base64Url by lazy { data.encodeToBase64Url() }
/**
* human-readable dump
*/
val dump by lazy { data.toDump() }
/**
* Concatenate two chunks and return new one
*/
operator fun plus(other: ByteChunk): ByteChunk = ByteChunk(data + other.data)
fun toByteArray(): ByteArray = data.asByteArray()
fun toUByteArray(): UByteArray = data
companion object {
fun fromHex(hex: String): ByteChunk = ByteChunk(hex.decodeHex().asUByteArray())
fun random(sizeInBytes: Int=16) = randomUBytes(sizeInBytes).toChunk()
}
}
private fun UByteArray.toChunk(): ByteChunk = ByteChunk(this)
@Suppress("unused")
private fun ByteArray.toChunk(): ByteChunk = ByteChunk(this.asUByteArray())
@Suppress("unused")
fun ByteArray.asChunk() = ByteChunk(toUByteArray())
fun UByteArray.asChunk(): ByteChunk = ByteChunk(this)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -20,6 +30,8 @@ import net.sergeych.crypto2.Container.Companion.createWith
* - [addRecipients] and various [plus] operators to add recipients * - [addRecipients] and various [plus] operators to add recipients
* - [updateData] to change decrypted content for the same recipient keys * - [updateData] to change decrypted content for the same recipient keys
* *
* Note that container _is serialized encrypted_.
*
* Some rules: * Some rules:
* *
* When adding public key recipient, it is faster to use your known [SecretKey], but you * When adding public key recipient, it is faster to use your known [SecretKey], but you
@ -126,8 +138,9 @@ sealed class Container {
abstract fun updateData(newPlainData: UByteArray, randomFill: IntRange? = null): Container abstract fun updateData(newPlainData: UByteArray, randomFill: IntRange? = null): Container
/** /**
* Binary encoded version. It is desirable to include [Container] as an object, though, * Binary encoded _encrypted_ version. It is desirable to include [Container] as an object, though,
* especially when using custom serialization (Json, Boss, etc), it is serializable. * especially when using custom serialization (Json, Boss, etc.), it is serializable. Note that
* serialized data is always encrypted.
* Still, if you need it in binary form, this is a shortcut. You can use [decode] or call * Still, if you need it in binary form, this is a shortcut. You can use [decode] or call
* [BipackDecoder.decode] to deserialize the binary form. * [BipackDecoder.decode] to deserialize the binary form.
*/ */
@ -152,6 +165,11 @@ sealed class Container {
var authorisedByKey: PublicKey? = null var authorisedByKey: PublicKey? = null
protected set protected set
/**
* List of [KeyId] of the keys that unlocked the container, in the same order used for encryption..
*/
abstract val keyIds: List<KeyId>
/** /**
* @suppress * @suppress
@ -172,6 +190,8 @@ sealed class Container {
override val decryptedWithKeyId: KeyId? override val decryptedWithKeyId: KeyId?
get() = decryptedWithKey?.id get() = decryptedWithKey?.id
override val keyIds: List<KeyId> = listOf(keyId)
init { init {
decryptedData = creationData decryptedData = creationData
} }
@ -267,6 +287,8 @@ sealed class Container {
override var decryptedWithKeyId: KeyId? = null override var decryptedWithKeyId: KeyId? = null
private set private set
override val keyIds: List<KeyId> = encryptedKeys.map { it.tag }
override fun decryptWith(keyRing: UniversalRing): UByteArray? { override fun decryptWith(keyRing: UniversalRing): UByteArray? {
decryptedData?.let { return it } decryptedData?.let { return it }
for (key in keyRing.decryptingKeys) { for (key in keyRing.decryptingKeys) {
@ -474,6 +496,12 @@ sealed class Container {
inline fun <reified T> decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? = inline fun <reified T> decrypt(cipherData: UByteArray, vararg keys: DecryptingKey): T? =
decryptAsUBytes(cipherData, *keys)?.let { BipackDecoder.decode<T>(it.asByteArray()) } decryptAsUBytes(cipherData, *keys)?.let { BipackDecoder.decode<T>(it.asByteArray()) }
inline fun <reified T> decrypt(cipherData: UByteArray, ring: UniversalRing): T? =
decode(cipherData)
.decryptWith(ring)?.let {
BipackDecoder.decode<T>(it.asByteArray())
}
fun decryptAsUBytes(cipherData: UByteArray, vararg keys: DecryptingKey): UByteArray? = fun decryptAsUBytes(cipherData: UByteArray, vararg keys: DecryptingKey): UByteArray? =
decode(cipherData).decryptWith(*keys) decode(cipherData).decryptWith(*keys)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import net.sergeych.bintools.CRC import net.sergeych.bintools.CRC

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray import com.ionspin.kotlin.crypto.util.decodeFromUByteArray

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.generichash.GenericHash import com.ionspin.kotlin.crypto.generichash.GenericHash
@ -7,7 +17,7 @@ import kotlinx.coroutines.flow.Flow
import org.kotlincrypto.hash.sha3.SHA3_256 import org.kotlincrypto.hash.sha3.SHA3_256
import org.kotlincrypto.hash.sha3.SHA3_384 import org.kotlincrypto.hash.sha3.SHA3_384
private interface StreamProcessor { interface StreamProcessor {
fun update(data: UByteArray) fun update(data: UByteArray)
fun final(): UByteArray fun final(): UByteArray
} }
@ -23,7 +33,7 @@ private interface StreamProcessor {
@Suppress("unused") @Suppress("unused")
enum class Hash( enum class Hash(
private val direct: ((UByteArray) -> UByteArray)? = null, private val direct: ((UByteArray) -> UByteArray)? = null,
private val streamProcessor: () -> StreamProcessor, val streamProcessor: () -> StreamProcessor,
) { ) {
Blake2b( Blake2b(
@ -112,6 +122,33 @@ enum class Hash(
return sp.final() return sp.final()
} }
/**
* Derive a salt of any size from a text. ___Salt could not be used as a password key___ source as it is not
* strong to brute force, but it is good when you just need to get a deterministic salt of arbitrary size.
*
* _Note that deriving salt of sizes less than hash block will reduce hash strength and should not be allowed
* in situation where strength is of concern while extending its length above hash block size does not improve
* it_. The only reason to use this function is when the desired _salt size_ is not equal to block size, or
* not known beforehand.1
*
* To get a cryptographically safe (more or less) key from password use [KDF] classes, or [KDF.deriveKey]
* and [KDF.deriveMultipleKeys].
*/
fun deriveSalt(base: String, sizeInBytes: Int): UByteArray {
require(sizeInBytes > 0)
val result = mutableListOf<UByte>()
var round = 0
var src = base.encodeToUByteArray()
do {
src = "rnd_${round++}_".encodeToUByteArray() + src
val hash = digest(src)
if (result.size + hash.size <= sizeInBytes) result += hash
else result += hash.slice(0..<(sizeInBytes - result.size))
} while (result.size < sizeInBytes)
return result.toUByteArray()
}
} }
private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray() private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray()
@ -122,6 +159,8 @@ private val defaultSuffix2 = "A stitch in time saves nine".encodeToUByteArray()
*/ */
fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.digest(src) fun blake2b(src: UByteArray): UByteArray = Hash.Blake2b.digest(src)
fun blake2b(src: ByteChunk): ByteChunk = blake2b(src.data).asChunk()
/** /**
* Double linked Blake2b using the default or specified suffix. This should be more hard to * Double linked Blake2b using the default or specified suffix. This should be more hard to
* brute force.collision attack than just [blake2b]. Note that different suffixes provide different * brute force.collision attack than just [blake2b]. Note that different suffixes provide different

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.LibsodiumInitializer import com.ionspin.kotlin.crypto.LibsodiumInitializer

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -18,6 +28,9 @@ import kotlinx.serialization.Serializable
* *
* See [PBKD.Params.deriveKey] for deriving keys from id. * See [PBKD.Params.deriveKey] for deriving keys from id.
* *
* See [id], and [BinaryId] class for more. Note that for [PublicKey] and [VerifyingPublicKey] [BinaryId.asPublicKey]
* and [BinaryId.asVerifyingKey] restore actual keys, providing [BinaryId.magic] has proper value, see [KeysmagicNumber]]
*
* @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same. * @param id actual id used in equality test amd hash code generation. `Id` of the matching keys is the same.
* @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper * @param kdp optional key derivation parameters. Does not affect equality. Allow deriving the key from proper
* password, see above. * password, see above.
@ -28,6 +41,8 @@ data class KeyId(val id: BinaryId, val kdp: PBKD.Params? = null) {
/** /**
* Binary array representation of the [id], not including the [kdp]. Used in [SafeKeyExchange] * Binary array representation of the [id], not including the [kdp]. Used in [SafeKeyExchange]
* and other key exchanges to generate session tokens, etc. * and other key exchanges to generate session tokens, etc.
*
* In shortcut for packed [BinaryId], from [id]. If you need only key bytes, use [UniversalKey.keyBytes].
*/ */
val binaryTag: UByteArray by lazy { id.id } val binaryTag: UByteArray by lazy { id.id }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
/** /**

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
enum class KeysmagicNumber(val label: String) { enum class KeysmagicNumber(val label: String) {
@ -6,6 +16,11 @@ enum class KeysmagicNumber(val label: String) {
defaultSymmetric( "sym"), defaultSymmetric( "sym"),
defaultSession( "ssn"), defaultSession( "ssn"),
defaultVerifying( "ver"), defaultVerifying( "ver"),
defaultSigningSecret( "sig"),
defaultUniversalPublic( "pub+"),
defaultUniversalPrivate( "prv+"),
; ;
} }

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.sergeych.bipack.Unsigned
import net.sergeych.crypto2.Multikey.AnyKey
import net.sergeych.crypto2.Multikey.Companion.allOf
import net.sergeych.crypto2.Multikey.Companion.allOfMultikeys
import net.sergeych.crypto2.Multikey.Companion.anyOf
import net.sergeych.crypto2.Multikey.Companion.anyOfMultikeys
import net.sergeych.crypto2.Multikey.Companion.invoke
import net.sergeych.crypto2.Multikey.Companion.someOf
import net.sergeych.crypto2.Multikey.Companion.someOfMultikeys
/**
* Multi-signed key.
* An arbitrary combination of [VerifyingPublicKey] to implement any multiple keys scenario, like N of M,
* and logical expression. Sample usage:
*
* ```kotlin
* val k1 = SigningSecretKey.new().verifyingKey
* val k2 = SigningSecretKey.new().verifyingKey
* val k3 = SigningSecretKey.new().verifyingKey
* val k4 = SigningSecretKey.new().verifyingKey
*
* val multikey = (k1 or k2) and (k3 or k4)
*
* val b: SealedBox = SealedBox.decode(someData)
*
* if( b.isSealedBy(multikey) ) {
* println("sealed box is properly sealed by a multikey")
* }
* ```
* To build multikeys, use `and` and `or` infix operators against [VerifyingPublicKey], [Multikey], or even
* [SigningSecretKey] instances, and shortcut methods:
*
* - [someOfMultikeys], [someOf] family for `n of M` logic
* - [anyOfMultikeys], [anyOf], [allOf], and [allOfMultikeys]
* - [invoke] for a single-key multikey
* - [AnyKey] when you need effectively match any key, useful when you need a `var` `Multikey`.
*
* __Important__. When serializing, always serialize as root [Multikey] instance to keep
* it compatible with any combination.
*/
@Serializable
sealed class Multikey {
/**
* Check that the [keys] satisfy the condition of this instance
*/
abstract fun check(keys: Iterable<VerifyingPublicKey>): Boolean
/**
* Check that [verifyingKeys] satisfy the multikey condition
*/
fun check(vararg verifyingKeys: VerifyingPublicKey): Boolean = check(verifyingKeys.asIterable())
infix fun or(mk: Multikey): Multikey = SomeOf(1, listOf(this, mk))
infix fun or(k: VerifyingPublicKey) = SomeOf(1, listOf(this, Multikey(k)))
infix fun or(k: SigningSecretKey) = SomeOf(1, listOf(this, Multikey(k.verifyingKey)))
infix fun and(mk: Multikey): Multikey = SomeOf(2, listOf(this, mk))
infix fun and(k: VerifyingPublicKey) = SomeOf(2, listOf(this, Multikey(k)))
infix fun and(k: SigningSecretKey) = SomeOf(2, listOf(this, Multikey(k.verifyingKey)))
/**
* Multikey instance implementing `m of N` logic against [VerifyingPublicKey] set. Do not use
* it directly, use any [Multikey.someOfMultikeys] functions instead.
*/
@Serializable
@SerialName("k")
class Keys internal constructor(
@Unsigned
val requiredMinimum: Int,
val validKeys: Set<VerifyingPublicKey>,
) : Multikey() {
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean {
var matches = 0
for (signer in keys) {
if (signer in validKeys) {
if (++matches >= requiredMinimum) return true
}
}
return false
}
}
/**
* Multikey instance implementing `m of N` logic against other [Multikey] instances. Do not use
* it directly, use any [Multikey.someOfMultikeys] functions instead.
*/
@Serializable
@SerialName("n")
class SomeOf internal constructor(
@Unsigned
val requiredMinimum: Int,
val validKeys: List<Multikey>,
) : Multikey() {
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean {
var matches = 0
for (k in validKeys) {
if (k.check(keys)) {
if (++matches >= requiredMinimum) return true
}
}
return false
}
}
/**
* Special `AnyKey`: no restrictions, any key will satisfy this. In the rare case to mark
* publicly available operations, etc. Please note it is an object, not a class, and can't
* be instantiated.
*/
@Serializable
object AnyKey : Multikey() {
override fun check(keys: Iterable<VerifyingPublicKey>): Boolean = true
}
companion object {
operator fun invoke(k: SigningSecretKey): Multikey = Keys(1, setOf(k.verifyingKey))
operator fun invoke(k: VerifyingPublicKey): Multikey = Keys(1, setOf(k))
/**
* Create a multikey instance that requires some keys from a list
*/
fun someOf(requiredMinimum: Int, vararg keys: VerifyingPublicKey): Multikey =
Keys(requiredMinimum, keys.toSet())
/**
* Create a multikey instance that requires some keys from a list
*/
fun someOfMultikeys(requiredMinimum: Int, vararg keys: Multikey): Multikey =
SomeOf(requiredMinimum, keys.toList())
/**
* Create a multikey instance that requires some keys from a list
*/
fun someOfMultikeys(requiredMinimum: Int, keys: List<Multikey>): Multikey =
SomeOf(requiredMinimum, keys)
/**
* Create a multikey instance that requires some keys from a list
*/
fun someOf(requiredMinimum: Int, keys: List<VerifyingPublicKey>): Multikey =
Keys(requiredMinimum, keys.toSet())
/**
* Create a multikey instance that requires any key from a list
*/
fun anyOf(vararg keys: VerifyingPublicKey): Multikey = someOf(1, *keys)
/**
* Create a multikey instance that requires any key from a list
*/
fun anyOfMultikeys(vararg keys: Multikey): Multikey = someOfMultikeys(1, *keys)
/**
* Create a multikey instance that requires any key from a list
*/
fun anyOfMultikeys(keys: List<Multikey>): Multikey = someOfMultikeys(1, keys)
/**
* Create a multikey instance that requires any key from a list
*/
fun anyOf(keys: List<VerifyingPublicKey>): Multikey = someOf(1, keys)
/**
* Create a multikey instance that requires all keys from a list
*/
fun allOf(vararg keys: VerifyingPublicKey): Multikey = someOf(keys.size, *keys)
/**
* Create a multikey instance that requires all keys from a list
*/
fun allOfMultikeys(vararg keys: Multikey): Multikey = someOfMultikeys(keys.size, *keys)
/**
* Create a multikey instance that requires all keys from a list
*/
fun allOfMultikeys(keys: List<Multikey>): Multikey = someOfMultikeys(keys.size, keys)
/**
* Create a multikey instance that requires all keys from a list
*/
fun allOf(keys: List<VerifyingPublicKey>): Multikey = someOf(keys.size, keys)
}
}

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
interface NonceBased { interface NonceBased {

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
/** /**

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray
@ -114,7 +124,7 @@ object PBKD {
} }
return entry.op { return entry.op {
if (entry.data == null) { if (entry.data == null) {
entry.data = entry.kdf.derive(password) entry.data = entry.kdf.deriveKey(password)
} }
entry.data!! entry.data!!
} }

View File

@ -1,8 +1,21 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import net.sergeych.bipack.decodeFromBipack
import net.sergeych.crypto2.VerifyingPublicKey.Companion.toString
import net.sergeych.mp_tools.decodeBase64Url
/** /**
* The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding * The public for public-key encryption. It encrypts messages that can only be decrypted with corresponding
@ -71,4 +84,40 @@ class PublicKey(override val keyBytes: UByteArray) : UniversalKey(), EncryptingK
Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill)) Asymmetric.createMessage(senderKey, this, WithFill.encode(plainData, randomFill))
override val id by lazy { KeyId(magic, keyBytes, null, true) } override val id by lazy { KeyId(magic, keyBytes, null, true) }
companion object {
/**
* Parse any known public key text representation, including what [toString] return (for public keys it is
* possible)
* @throws IllegalArgumentException the public key isn't recognized
*/
fun parse(text: String): PublicKey {
val s = text.trim()
fun parseId(t: String): PublicKey{
val id = BinaryId.restoreFromString(t)
if( id.magic != KeysmagicNumber.defaultAssymmetric.ordinal)
throw IllegalArgumentException("invalid magick ${id.magic} for PublicKey")
return id.asPublicKey
}
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
return when {
s.startsWith("\uD83D\uDDDDpub#") -> parseId(s.drop(6))
s.startsWith("pub#") -> parseId(s.drop(4))
s.startsWith("#") -> parseId(s.drop(1))
else -> {
// consider it is serialized key in base64 format
val data = s.decodeBase64Url().asUByteArray()
if (data.size == 32)
PublicKey(data)
else {
runCatching { data.decodeFromBipack<PublicKey>() }.getOrNull()
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as PublicKey }
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
}
}
}
}
}
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.keyexchange.KeyExchange import com.ionspin.kotlin.crypto.keyexchange.KeyExchange

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@ -6,6 +16,7 @@ import net.sergeych.bipack.BipackEncoder
import net.sergeych.bipack.decodeFromBipack import net.sergeych.bipack.decodeFromBipack
import net.sergeych.crypto2.Seal.Companion.create import net.sergeych.crypto2.Seal.Companion.create
import net.sergeych.utools.now import net.sergeych.utools.now
import kotlin.time.Duration.Companion.seconds
/** /**
* Extended public-key signature. * Extended public-key signature.
@ -67,7 +78,7 @@ class Seal(
*/ */
fun verify(message: UByteArray) { fun verify(message: UByteArray) {
val n = now() val n = now()
if (createdAt > n) throw IllegalSignatureException("signature's timestamp in the future") if (createdAt - 45.seconds > n) throw IllegalSignatureException("signature's timestamp in the future: $createdAt / $n")
expiresAt?.let { expiresAt?.let {
if (n >= it) throw ExpiredSignatureException("signature expired at $it") if (n >= it) throw ExpiredSignatureException("signature expired at $it")
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
@ -6,6 +16,7 @@ import kotlinx.serialization.Transient
import net.sergeych.bipack.BipackDecoder import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder import net.sergeych.bipack.BipackEncoder
import net.sergeych.bipack.decodeFromBipack import net.sergeych.bipack.decodeFromBipack
import net.sergeych.utools.pack
/** /**
* Multi-signed data box. Do not use the constructori directly, use [SealedBox.create] * Multi-signed data box. Do not use the constructori directly, use [SealedBox.create]
@ -27,11 +38,22 @@ import net.sergeych.bipack.decodeFromBipack
@Serializable @Serializable
class SealedBox( class SealedBox(
val message: UByteArray, val message: UByteArray,
private val seals: List<Seal>, /**
* [Seal] instance representing _correct signatures_ of this box. Note that if the box
* is constructed (deserialized, etc) successfully, all seals are ok. Initial check
* of signatures could be bypassed by setting [checkOnInit] to false, which should
* be avoided.
*/
val seals: List<Seal>,
@Transient @Transient
private val checkOnInit: Boolean = true private val checkOnInit: Boolean = true
) { ) {
/**
* Extract [VerifyingPublicKey] from [seals].
*/
val signedByKeys: List<VerifyingPublicKey> by lazy { seals.map { it.publicKey } }
@Suppress("unused") @Suppress("unused")
constructor(message: UByteArray, vararg keys: SigningKey) : constructor(message: UByteArray, vararg keys: SigningKey) :
this(message, keys.map { it.seal(message) } ) this(message, keys.map { it.seal(message) } )
@ -49,7 +71,7 @@ class SealedBox(
* Add expiring seal, otherwise use [plus]. Overrides exising seal for [key] * Add expiring seal, otherwise use [plus]. Overrides exising seal for [key]
* if present: * if present:
*/ */
fun addSeal(key: SigningKey, expiresAt: Instant): SealedBox { fun addSeal(key: SigningKey, expiresAt: Instant?): SealedBox {
val filtered = seals.filter { it.publicKey != key.verifyingKey } val filtered = seals.filter { it.publicKey != key.verifyingKey }
return SealedBox(message, filtered + key.seal(message, expiresAt), false) return SealedBox(message, filtered + key.seal(message, expiresAt), false)
} }
@ -61,6 +83,18 @@ class SealedBox(
return seals.any { it.publicKey == publicKey } return seals.any { it.publicKey == publicKey }
} }
/**
* Checks that the box is signed by enough keys to satisfy the given [Multikey].
*/
@Suppress("unused")
fun isSealedBy(multikey: Multikey) = multikey.check(signedByKeys)
/**
* Unpack bipack-encoded payload
*/
@Suppress("unused")
inline fun <reified T>unpack(): T = BipackDecoder.decode(message)
init { init {
if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal") if (seals.isEmpty()) throw IllegalArgumentException("there should be at least one seal")
if (checkOnInit) { if (checkOnInit) {
@ -85,6 +119,19 @@ class SealedBox(
return SealedBox(data, keys.map { it.seal(data) }, false) return SealedBox(data, keys.map { it.seal(data) }, false)
} }
/**
* Create a new instance serializing given data with Bipack and some
* keys. At least one key is required to disallow providing not-signed
* instances, e.g. [SealedBox] is guaranteed to be properly sealed when
* successfully instantiated.
*
* @param payload an object to serialize and sign
* @param keys a list of keys to sign with, should be at least one key.
* @throws IllegalArgumentException if keys are not specified.
*/
inline fun <reified T>new(payload: T,vararg keys: SigningKey): SealedBox =
create(pack(payload), *keys)
inline fun <reified T>encode(value: T, vararg keys: SigningKey): UByteArray = inline fun <reified T>encode(value: T, vararg keys: SigningKey): UByteArray =
create(BipackEncoder.encode(value).toUByteArray(), *keys).encoded create(BipackEncoder.encode(value).toUByteArray(), *keys).encoded

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.box.Box import com.ionspin.kotlin.crypto.box.Box

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.datetime.Instant import kotlinx.datetime.Instant

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.Signature import com.ionspin.kotlin.crypto.signature.Signature
@ -23,16 +33,45 @@ class SigningSecretKey(
VerifyingPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it } VerifyingPublicKey(Signature.ed25519SkToPk(keyBytes)).also { cachedPublicKey = it }
} }
override val magic: KeysmagicNumber = KeysmagicNumber.defaultSigningSecret
override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes) override fun sign(message: UByteArray): UByteArray = Signature.detached(message, keyBytes)
override fun seal(message: UByteArray, expiresAt: Instant?): Seal = override fun seal(message: UByteArray, expiresAt: Instant?): Seal =
Seal.create(this, message, now(), expiresAt) Seal.create(this, message, now(), expiresAt)
@Transient
override val id: KeyId = verifyingKey.id override val id: KeyId = verifyingKey.id
override val label: String override val label: String
get() = "sig" get() = "sig"
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: VerifyingPublicKey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: SigningSecretKey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: Multikey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this and [other] key
*/
infix fun and(other: VerifyingPublicKey) = Multikey(this) and other
/**
* Create a [Multikey] that requires presence of this and [other] key
*/
infix fun and(other: SigningSecretKey) = Multikey(this) and other
/**
* Create a [Multikey] that requires presence of this and [other] key
*/
infix fun and(other: Multikey) = Multikey(this) and other
companion object { companion object {
data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: VerifyingPublicKey) data class SigningKeyPair(val secretKey: SigningSecretKey, val publicKey: VerifyingPublicKey)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.secretbox.SecretBox import com.ionspin.kotlin.crypto.secretbox.SecretBox
@ -46,7 +56,7 @@ class SymmetricKey(
override val nonceBytesLength: Int = nonceLength override val nonceBytesLength: Int = nonceLength
override val id by lazy { override val id by lazy {
KeyId(KeysmagicNumber.defaultSymmetric,blake2b3l(keyBytes), pbkdfParams) KeyId(KeysmagicNumber.defaultSymmetric,blake2b3l(keyBytes),pbkdfParams)
} }
override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray = override fun decryptWithNonce(cipherData: UByteArray, nonce: UByteArray): UByteArray =

View File

@ -1,18 +1,39 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import net.sergeych.bipack.BipackEncoder
import net.sergeych.bipack.decodeFromBipack
import net.sergeych.mp_tools.decodeBase64Compact
import net.sergeych.mp_tools.encodeToBase64Compact
@Serializable @Serializable
sealed class UniversalKey: KeyInstance { sealed class UniversalKey : KeyInstance {
abstract val keyBytes: UByteArray abstract val keyBytes: UByteArray
@Transient @Transient
open val magic: KeysmagicNumber = KeysmagicNumber.Unknown open val magic: KeysmagicNumber = KeysmagicNumber.Unknown
/**
* Key ID positively identify key from the point of view of _decrypting or verifying_. So matching [VerifyingKey]
* and [SigningKey] will have the same id, same as matching [PublicKey] and [SecretKey].
*
* KeyId is based on [BinaryId] which includes checksum (crc8) and magick number for additional security,
* see [KeysmagicNumber].
*
* Also "public" keys can be restored from id using [BinaryId.asPublicKey] and [BinaryId.asVerifyingKey].
*/
override val id by lazy { KeyId(magic, keyBytes, null) } override val id by lazy { KeyId(magic, keyBytes, null) }
// Important: id can be overridden, so we use it, not magic: // Important: id can be overridden, so we use it, not magic:
@ -34,12 +55,34 @@ sealed class UniversalKey: KeyInstance {
companion object { companion object {
fun newSecretKey() = SecretKey.new() fun newSecretKey() = SecretKey.new()
fun newSigningKey() = SigningSecretKey.new() fun newSigningKey() = SigningSecretKey.new()
@Suppress("unused") @Suppress("unused")
fun newSymmetricKey() = SymmetricKey.new() fun newSymmetricKey() = SymmetricKey.new()
/**
* Parse all known string representations of the universal key
* @throws IllegalArgumentException if it can't parse any key.
*/
fun parseString(text: String): UniversalKey {
val s = text.trim()
return when {
s.startsWith("\uD83D\uDDDDpub#") || s.startsWith("pub#") ->
PublicKey.parse(s)
s.startsWith("\uD83D\uDDDDver#") || s.startsWith("ver#") ->
VerifyingPublicKey.parse(s)
else -> {
s.decodeBase64Compact().decodeFromBipack<UniversalKey>()
}
}
}
} }
} }
inline fun <reified T : UniversalKey> T.asString() =
BipackEncoder.encode<T>(this).encodeToBase64Compact()
open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") : open class IllegalSignatureException(text: String = "signed data is tampered or signature is corrupted") :
IllegalStateException(text) IllegalStateException(text)

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
/**
* Combination of private/secret keys suitable for both decryption and signing.
*
* It contains two cryptographically independent keys to raise security to a maximum.
* Any converted keys poses a threat while technically possible so we avoid it.
*/
@Serializable
@SerialName("uprv")
class UniversalPrivateKey(
val signingKey: SigningSecretKey,
val decryptingKey: SecretKey
) : UniversalKey(), DecryptingKey by decryptingKey, SigningKey by signingKey {
override val keyBytes by lazy { signingKey.keyBytes + decryptingKey.keyBytes }
@Transient
override val magic = KeysmagicNumber.defaultUniversalPrivate
/**
* Important! Private key combines signing and decrypting keys, but uses
* it of the decrypting one to be used in keyring.
*/
@Transient
override val id: KeyId = decryptingKey.id
/**
* Corresponding public key able to verify amd encrypt data created by this
* private key.
*/
val publicKey by lazy {
UniversalPublicKey(signingKey.verifyingKey, decryptingKey.publicKey)
}
companion object {
/**
* Generate 2 new random keys (4 key pairs under the hood) to securely signd and
* decrypt data.
*/
fun new() = UniversalPrivateKey(SigningSecretKey.new(), SecretKey.new())
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
/**
* Combination of public keys suitable for both encryption and verification. A counterpart
* of the [UniversalPrivateKey], available also as [UniversalPrivateKey.publicKey].
*
* When using [UniversalRing] and [Container], data encrypted with instances og this class
* can be decrypted with rings containing the corresponding [UniversalPrivateKey].
*/
@Serializable
@SerialName("upub")
class UniversalPublicKey(
val verifyingKey: VerifyingPublicKey,
val encryptingKey: PublicKey
): UniversalKey(), VerifyingKey by verifyingKey, EncryptingKey by encryptingKey{
override val keyBytes by lazy { verifyingKey.keyBytes + encryptingKey.keyBytes }
@Transient
override val magic = KeysmagicNumber.defaultUniversalPublic
/**
* Important! Private key combines signing and decrypting keys, but uses
* it of the decrypting one to be used in keyring.
*/
@Transient
override val id: KeyId = encryptingKey.id
}

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -62,28 +72,34 @@ class UniversalRing(
* Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about * Get all keys for the specified id (normally it could be 0, 1 or 2). See [KeyId] about
* matching id keys. * matching id keys.
*/ */
fun keysById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id } fun findById(id: KeyId): List<UniversalKey> = allKeys.filter { it.id == id }
@Deprecated("please replace", replaceWith = ReplaceWith("findById"))
fun keysById(id: KeyId) = findById(id)
/** /**
* Return sequence of keys that have at least one of the [tags] * Return sequence of keys that have at least one of the [tags]
*/ */
fun keysByTags(vararg tags: String) = sequence { fun findByTags(vararg tags: String) = sequence {
for (e in keyWithTags.entries) { for (e in keyWithTags.entries) {
if (tags.any { it in e.value }) yield(e.key) if (tags.any { it in e.value }) yield(e.key)
} }
} }
@Deprecated("please replace", replaceWith = ReplaceWith("findByTag"))
fun keysByTag(vararg tags: String) = findByTags(*tags)
/** /**
* Get the first key of the specified type having the [tag] * Get the first key of the specified type having the [tag]
*/ */
inline fun <reified T> keyByTag(tag: String) = keysByTags(tag).first { it is T } inline fun <reified T> keyByTag(tag: String) = findByTags(tag).first { it is T }
/** /**
* Get keys of the specified type having any of the specified tags associated. * Get keys of the specified type having any of the specified tags associated.
*/ */
@Suppress("unused") @Suppress("unused")
inline fun <reified T> keysByAnyTag(vararg tags: String): Sequence<UniversalKey> = inline fun <reified T> keysByAnyTag(vararg tags: String): Sequence<UniversalKey> =
keysByTags(*tags).filter { it is T } findByTags(*tags).filter { it is T }
/** /**
* Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more. * Get all keys with a given id. Note that _matching keys_ have the same id, see [KeyId] for more.
@ -134,6 +150,7 @@ class UniversalRing(
/** /**
* Add key and tags to the ring. If the key already exists, tags are merged. * Add key and tags to the ring. If the key already exists, tags are merged.
*/ */
@Suppress("unused")
fun add(key: UniversalKey, tags: Collection<String>): UniversalRing = fun add(key: UniversalKey, tags: Collection<String>): UniversalRing =
UniversalRing(keyWithTags + (key to tags.toSet())) UniversalRing(keyWithTags + (key to tags.toSet()))

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
interface VerifyingKey: KeyInstance { interface VerifyingKey: KeyInstance {

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.signature.InvalidSignatureException import com.ionspin.kotlin.crypto.signature.InvalidSignatureException
@ -5,6 +15,8 @@ import com.ionspin.kotlin.crypto.signature.Signature
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import net.sergeych.bipack.decodeFromBipack
import net.sergeych.mp_tools.decodeBase64Url
/** /**
* Public key to verify signatures only * Public key to verify signatures only
@ -26,4 +38,71 @@ class VerifyingPublicKey(override val keyBytes: UByteArray) : UniversalKey(), Ve
override val magic: KeysmagicNumber = KeysmagicNumber.defaultVerifying override val magic: KeysmagicNumber = KeysmagicNumber.defaultVerifying
override val id by lazy { KeyId(magic, keyBytes, null, true) } override val id by lazy { KeyId(magic, keyBytes, null, true) }
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: VerifyingPublicKey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: SigningSecretKey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this or [other] key
*/
infix fun or(other: Multikey) = Multikey(this) or other
/**
* Create a [Multikey] that requires presence of this and [other]
*/
infix fun and(other: VerifyingPublicKey) = Multikey(this) and other
/**
* Create a [Multikey] that requires presence of this and [other]
*/
infix fun and(other: SigningSecretKey) = Multikey(this) and other
/**
* Create a [Multikey] that requires presence of this and [other]
*/
infix fun and(other: Multikey) = Multikey(this) and other
companion object {
/**
* Parse any known public key text representation, including what [toString] return (for public keys it is
* possible)
* @throws IllegalArgumentException the public key isn't recognized, in particular [BinaryId.InvalidException]
* if the text is corrupt
*/
fun parse(text: String): VerifyingPublicKey {
val s = text.trim()
fun parseId(t: String): VerifyingPublicKey {
// assume it is an id:
val id = BinaryId.restoreFromString(t)
return if (id.magic == KeysmagicNumber.defaultVerifying.ordinal)
id.asVerifyingKey as VerifyingPublicKey
else throw IllegalArgumentException("Invalid magick: ${id.magic} when parsing[$t]")
}
// 🗝sig#I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg
return when {
s.startsWith("\uD83D\uDDDDver#") -> parseId(s.drop(6))
s.startsWith("ver#") -> parseId(s.drop(4))
s.startsWith("#") -> parseId(s.drop(1))
else -> {
// consider it is serialized key in base64 format
val data = s.decodeBase64Url().asUByteArray()
if (data.size == 32)
VerifyingPublicKey(data)
else if( data.size == 34) {
// raw id
BinaryId(data).asVerifyingKey as VerifyingPublicKey
}else {
runCatching { data.decodeFromBipack<VerifyingPublicKey>() }.getOrNull()
?: kotlin.runCatching { data.decodeFromBipack<UniversalKey>() as VerifyingPublicKey }
.getOrElse { throw IllegalArgumentException("can't parse verifying key") }
}
}
}
}
}
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
/** /**

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.crypto2 package net.sergeych.crypto2
import com.ionspin.kotlin.crypto.pwhash.* import com.ionspin.kotlin.crypto.pwhash.*
@ -27,12 +37,47 @@ sealed class KDF {
Moderate, Moderate,
FixedHigher, FixedHigher,
Sensitive, Sensitive,
;
/**
* Create [KDF] of the corresponding strength suitable to derive [numberOfKeys] symmetric keys.
*
* Random salt of proper size is used
*/
fun kdfForSize(numberOfKeys: Int): KDF = creteDefault(SymmetricKey.keyLength * numberOfKeys, this)
/**
* Derive multiple keys from the password. Derivation params will be included in the key ids, see
* [SymmetricKey.id] as [KeyId.kdp].
* Random salt of proper size is used
*
* ___Important: symmetric keys do not save key ids___. _Container do it, so it is possible to re-derive
* key to open the container, but in many cases you might need to save [KeyId.kdp] separately_.
* Having no [PBKD.Params] it would not be possible to recreate the key, as complexity defaults tend
* to change with time.
*/
@Suppress("unused")
fun deriveMultiple(password: String, count: Int): List<SymmetricKey> =
kdfForSize(count).deriveMultipleKeys(password, count)
/**
* Derive single key from password, same as [deriveMultiple] with count=1.
*/
fun derive(password: String): SymmetricKey = deriveMultiple(password, 1).first()
} }
abstract fun derive(password: String): UByteArray /**
* Derive a single key from the password, same as [deriveMultipleKeys] with `count==1`
*/
abstract fun deriveKey(password: String): UByteArray
fun deriveMultiple(password: String, count: Int): List<SymmetricKey> { /**
val bytes = derive(password) * Derive keys from lower part of bytes derived from the password. E.g., if generated size is longer than
* required to create [count] keys, the first bytes will be used. It let use rest bytes for other purposes.
*/
fun deriveMultipleKeys(password: String, count: Int): List<SymmetricKey> {
val bytes = deriveKey(password)
val ks = SymmetricKey.keyLength val ks = SymmetricKey.keyLength
check(ks * count <= bytes.size) { "KDF is too short for $count keys: ${bytes.size} we need ${ks * count}" } check(ks * count <= bytes.size) { "KDF is too short for $count keys: ${bytes.size} we need ${ks * count}" }
return (0..<count).map { return (0..<count).map {
@ -54,6 +99,7 @@ sealed class KDF {
val keySize: Int, val keySize: Int,
) : KDF(), Comparable<Argon> { ) : KDF(), Comparable<Argon> {
/** /**
* Very abstract strength comparison. If a1 > a2 it generally means that its complexity, and, hopefully, * Very abstract strength comparison. If a1 > a2 it generally means that its complexity, and, hopefully,
* strength is higher. * strength is higher.
@ -81,7 +127,7 @@ sealed class KDF {
} }
} }
override fun derive(password: String): UByteArray = override fun deriveKey(password: String): UByteArray =
PasswordHash.pwhash(keySize, password, salt, instructionsComplexity, memComplexity, algorithm.code) PasswordHash.pwhash(keySize, password, salt, instructionsComplexity, memComplexity, algorithm.code)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@ -111,6 +157,17 @@ sealed class KDF {
fun randomSalt() = randomUBytes(saltSize) fun randomSalt() = randomUBytes(saltSize)
/**
* Create a deterministic salt suitable fot this KDF from a given text.
*
* We recommend to use random salts stored, and [KeyId] of password-generated keys already
* do it for you. Use this method only if you can't store [KeyId] or salt; it is generally less secure:
* knowing the base text it is possible to understand, for example, that the same derived password was used
* more than once (random salt makes it impossible).
*/
@Suppress("unused")
fun deriveSaltFromString(text: String) = Hash.Blake2b.deriveSalt(text, saltSize)
fun create(complexity: Complexity, salt: UByteArray, keySize: Int): Argon { fun create(complexity: Complexity, salt: UByteArray, keySize: Int): Argon {
require(salt.size == saltSize) { "The salt size should be $saltSize" } require(salt.size == saltSize) { "The salt size should be $saltSize" }
require(keySize > minKeySize) { "The key size should be at least $keySize bytes" } require(keySize > minKeySize) { "The key size should be at least $keySize bytes" }
@ -135,6 +192,7 @@ sealed class KDF {
268435456, 268435456,
salt, keySize salt, keySize
) )
Moderate -> Argon( Moderate -> Argon(
Alg.default, Alg.default,
crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_OPSLIMIT_MODERATE,
@ -142,14 +200,14 @@ sealed class KDF {
salt, keySize salt, keySize
) )
Complexity.Sensitive -> Argon( Sensitive -> Argon(
Alg.default, Alg.default,
crypto_pwhash_OPSLIMIT_SENSITIVE, crypto_pwhash_OPSLIMIT_SENSITIVE,
crypto_pwhash_MEMLIMIT_SENSITIVE, crypto_pwhash_MEMLIMIT_SENSITIVE,
salt, keySize salt, keySize
) )
Complexity.FixedHigher -> Argon( FixedHigher -> Argon(
V2id_13, V2id_13,
4UL, 4UL,
1073741824, 1073741824,
@ -162,10 +220,10 @@ sealed class KDF {
} }
companion object { companion object {
@Suppress("unused")
fun creteDefault(keySize: Int, complexity: Complexity, salt: UByteArray = Argon.randomSalt()): KDF { fun creteDefault(keySize: Int, complexity: Complexity, salt: UByteArray = Argon.randomSalt()): KDF {
return Argon.create(complexity, salt, keySize) return Argon.create(complexity, salt, keySize)
} }
} }
data class Instance(val kdf: KDF, val password: String) data class Instance(val kdf: KDF, val password: String)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.sergeych.crypto2 package net.sergeych.crypto2

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.sergeych.crypto2 package net.sergeych.crypto2

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.tools package net.sergeych.tools
@Suppress("unused") @Suppress("unused")

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.tools package net.sergeych.tools
import net.sergeych.synctools.ProtectedOp import net.sergeych.synctools.ProtectedOp
@ -5,7 +15,7 @@ import net.sergeych.synctools.invoke
/** /**
* Multiplatform (JS and battery included) atomically mutable value. * Multiplatform (JS and battery included) atomically mutable value.
* Actual value can be either changed in a block of [mutuate] when * Actual value can be either changed in a block of [mutate] when
* new value _depends on the current value_ or use a same [value] * new value _depends on the current value_ or use a same [value]
* property that is thread-safe where there are threads and just safe * property that is thread-safe where there are threads and just safe
* otherwise ;) * otherwise ;)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.sergeych.tools package net.sergeych.tools

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.tools package net.sergeych.tools
import net.sergeych.bipack.BipackDecoder import net.sergeych.bipack.BipackDecoder

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.sergeych.utools package net.sergeych.utools

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package net.sergeych.utools package net.sergeych.utools
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package net.sergeych.utools package net.sergeych.utools

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package org.komputing.khash.keccak package org.komputing.khash.keccak
import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.BigInteger

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package org.komputing.khash.keccak package org.komputing.khash.keccak

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
package org.komputing.khash.keccak.extensions package org.komputing.khash.keccak.extensions
/** /**

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
@file:Suppress("unused") @file:Suppress("unused")
package org.komputing.khash.keccak.extensions package org.komputing.khash.keccak.extensions

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.toDump
import net.sergeych.bipack.BipackEncoder
import net.sergeych.crypto2.BinaryId
import net.sergeych.crypto2.ByteChunk
import net.sergeych.crypto2.initCrypto
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
class BinaryIdTest {
@Test
fun testSizes() {
val a = BinaryId.createRandom(5, 4)
// println(a.id.toDump())
// println(pack(a).toDump())
assertEquals(2, a.body.size)
assertEquals(5, a.magic)
assertEquals(4, a.id.size)
}
@Test
fun testByteChunkSizes() = runTest {
initCrypto()
val x = ByteChunk.random(3)
assertEquals(3, x.data.size)
assertEquals(3, x.toByteArray().size)
assertEquals(3, x.toUByteArray().size)
println(BipackEncoder.encode(x).toDump())
assertEquals(4, BipackEncoder.encode(x).size)
assertContentEquals(BipackEncoder.encode(x.toByteArray()), BipackEncoder.encode(x))
}
}

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
@ -5,7 +15,10 @@ import net.sergeych.crypto2.Hash
import net.sergeych.crypto2.initCrypto import net.sergeych.crypto2.initCrypto
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextUBytes import kotlin.random.nextUBytes
import kotlin.test.* import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFalse
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE") @Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
suspend fun <T> sw(label: String, f: suspend () -> T): T { suspend fun <T> sw(label: String, f: suspend () -> T): T {
@ -46,5 +59,21 @@ class HashTest {
assertContentEquals(Hash.Blake2b.digest(a), p1) assertContentEquals(Hash.Blake2b.digest(a), p1)
assertContentEquals(Hash.Sha3_384.digest(a), p2) assertContentEquals(Hash.Sha3_384.digest(a), p2)
} }
@Test
fun deriveSaltTest() = runTest {
initCrypto()
for( i in 2..257 ) {
val x = Hash.Sha3AndBlake.deriveSalt("base one", i)
val y = Hash.Sha3AndBlake.deriveSalt("base one", i)
val z = Hash.Sha3AndBlake.deriveSalt("base two", i)
assertContentEquals(x, y)
assertFalse { x contentEquals z }
assertEquals(x.size, i)
assertEquals(y.size, i)
assertEquals(z.size, i)
}
}
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.KDF import net.sergeych.crypto2.KDF
import net.sergeych.crypto2.initCrypto import net.sergeych.crypto2.initCrypto
@ -38,4 +48,11 @@ class KDFTest {
assertEquals(set2, set1) assertEquals(set2, set1)
} }
@Test
fun complexityTest() = runTest{
initCrypto()
val kk = KDF.Complexity.Interactive.kdfForSize(3).deriveMultipleKeys("lala", 3)
assertEquals(3, kk.size)
}
} }

View File

@ -1,8 +1,20 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import com.ionspin.kotlin.crypto.util.decodeFromUByteArray import com.ionspin.kotlin.crypto.util.decodeFromUByteArray
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
import net.sergeych.crypto2.* import net.sergeych.crypto2.*
import net.sergeych.tools.bipack import net.sergeych.tools.bipack
import net.sergeych.tools.biunpack import net.sergeych.tools.biunpack
@ -15,9 +27,9 @@ class KeysTest {
@Test @Test
fun testSigningCreationAndMap() = runTest { fun testSigningCreationAndMap() = runTest {
initCrypto() initCrypto()
val (stk,pbk) = SigningSecretKey.generatePair() val (stk, pbk) = SigningSecretKey.generatePair()
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])
val s1 = SigningSecretKey(stk.keyBytes) val s1 = SigningSecretKey(stk.keyBytes)
assertEquals(stk, s1) assertEquals(stk, s1)
@ -52,7 +64,7 @@ class KeysTest {
fun testNonDeterministicSeals() = runTest { fun testNonDeterministicSeals() = runTest {
initCrypto() initCrypto()
val data = "Welcome to the crazy new world!".encodeToUByteArray() val data = "Welcome to the crazy new world!".encodeToUByteArray()
val (sk,_) = SigningSecretKey.generatePair() val (sk, _) = SigningSecretKey.generatePair()
val t = now() val t = now()
val s1 = Seal.create(sk, data, createdAt = t) val s1 = Seal.create(sk, data, createdAt = t)
val s2 = Seal.create(sk, data, createdAt = t) val s2 = Seal.create(sk, data, createdAt = t)
@ -60,11 +72,11 @@ class KeysTest {
val s3 = Seal.create(sk, data, createdAt = t, nonDeterministic = true) val s3 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
val s4 = Seal.create(sk, data, createdAt = t, nonDeterministic = true) val s4 = Seal.create(sk, data, createdAt = t, nonDeterministic = true)
for( seal in listOf(s1,s2,s3,s4)) { for (seal in listOf(s1, s2, s3, s4)) {
assertTrue { seal.isValid(data) } assertTrue { seal.isValid(data) }
assertTrue { Seal.unpack(seal.packed).isValid(data) } assertTrue { Seal.unpack(seal.packed).isValid(data) }
} }
assertFalse { s2bad.isValid(data)} assertFalse { s2bad.isValid(data) }
assertContentEquals(s1.packed, s2.packed) assertContentEquals(s1.packed, s2.packed)
assertFalse { s1.packed contentEquals s3.packed } assertFalse { s1.packed contentEquals s3.packed }
assertFalse { s4.packed contentEquals s3.packed } assertFalse { s4.packed contentEquals s3.packed }
@ -173,7 +185,7 @@ class KeysTest {
val (sk0, pk0) = Asymmetric.generateKeys() val (sk0, pk0) = Asymmetric.generateKeys()
// println(sk0.publicKey) // println(sk0.publicKey)
val j = Json { prettyPrint = true} val j = Json { prettyPrint = true }
val sk1 = j.decodeFromString<SecretKey>(j.encodeToString(sk0)) val sk1 = j.decodeFromString<SecretKey>(j.encodeToString(sk0))
assertEquals(sk0, sk1) assertEquals(sk0, sk1)
@ -274,6 +286,188 @@ class KeysTest {
assertContentEquals(k.verifyingKey.keyBytes, dk2.id.binaryTag.take(32).toUByteArray()) assertContentEquals(k.verifyingKey.keyBytes, dk2.id.binaryTag.take(32).toUByteArray())
assertContentEquals(k.verifyingKey.keyBytes, dk1.id.binaryTag.take(32).toUByteArray()) assertContentEquals(k.verifyingKey.keyBytes, dk1.id.binaryTag.take(32).toUByteArray())
// and restored from id should be the same: // and restored from id should be the same:
assertEquals( k.verifyingKey, dk2.id.id.asVerifyingKey) assertEquals(k.verifyingKey, dk2.id.id.asVerifyingKey)
}
@Test
fun multiKeyTestSome() = runTest {
initCrypto()
val k1 = SigningSecretKey.new()
val k2 = SigningSecretKey.new()
val k3 = SigningSecretKey.new()
val k4 = SigningSecretKey.new()
val k5 = SigningSecretKey.new()
// val k6 = SigningSecretKey.new()
val mk: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey))
val mk23: Multikey = Multikey.Keys(2, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
val mk13: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
assertTrue { mk.check(k1.verifyingKey) }
assertFalse { mk.check(k2.verifyingKey) }
assertTrue { mk23.check(k1.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
assertTrue { mk23.check(k3.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
assertFalse { mk23.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
assertTrue { mk13.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
println(pack(mk23).toDump())
println(pack(mk23).size)
val smk23: Multikey = Multikey.someOf(2, k1.verifyingKey, k2.verifyingKey, k3.verifyingKey)
// val smk13: Multikey = Multikey.Keys(1, setOf(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey))
assertTrue { smk23.check(k1.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
assertTrue { smk23.check(k3.verifyingKey, k2.verifyingKey, k4.verifyingKey) }
assertFalse { smk23.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
// assertTrue { smk13.check(k4.verifyingKey, k2.verifyingKey, k5.verifyingKey) }
println(pack(smk23).toDump())
println(pack(smk23).size)
val s1 = k1 or k2 or k3
println(pack(s1).toDump())
println(pack(s1).size)
assertTrue { s1.check(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey) }
assertTrue { s1.check(k1.verifyingKey) }
assertTrue { s1.check(k2.verifyingKey) }
assertTrue { s1.check(k3.verifyingKey) }
assertFalse { s1.check(k4.verifyingKey) }
val s2 = (k1 or k2) and k3
println(pack(s2).toDump())
println(pack(s2).size)
assertTrue { s2.check(k1.verifyingKey, k3.verifyingKey) }
assertTrue { s2.check(k2.verifyingKey, k3.verifyingKey) }
assertTrue { s2.check(k1.verifyingKey, k2.verifyingKey, k3.verifyingKey) }
assertFalse { s2.check(k4.verifyingKey) }
assertFalse { s2.check(k1.verifyingKey) }
assertFalse { s2.check(k2.verifyingKey) }
assertFalse { s2.check(k3.verifyingKey) }
assertFalse { s2.check(k1.verifyingKey, k2.verifyingKey) }
val s3 = (k1 and k2) or k3
println(pack(s3).toDump())
println(pack(s3).size)
assertTrue { s3.check(k1.verifyingKey, k3.verifyingKey) }
assertTrue { s3.check(k3.verifyingKey) }
assertTrue { s3.check(k2.verifyingKey, k1.verifyingKey) }
assertFalse { s3.check(k1.verifyingKey) }
assertFalse { s3.check(k2.verifyingKey) }
assertFalse { s3.check(k1.verifyingKey, k4.verifyingKey) }
}
@Test
fun multiKeyTestAny() = runTest {
initCrypto()
val k1 = SigningSecretKey.new()
val k2 = SigningSecretKey.new()
val k3 = SigningSecretKey.new()
val k4 = SigningSecretKey.new()
val k5 = SigningSecretKey.new()
// val k6 = SigningSecretKey.new()
val mk: Multikey = Multikey.AnyKey
assertTrue { mk.check(k1.verifyingKey) }
assertTrue { mk.check(k2.verifyingKey) }
assertTrue { mk.check(k3.verifyingKey) }
assertTrue { mk.check(k4.verifyingKey) }
assertTrue { mk.check(k5.verifyingKey) }
}
@Test
fun testCombinedKeys() = runTest {
initCrypto()
val k1 = UniversalPrivateKey.new()
val k2 = UniversalPrivateKey.new()
val k3: UniversalPrivateKey = unpack(pack(k1))
assertEquals(k1, k3)
assertEquals(k1.publicKey, k3.publicKey)
assertEquals(k1.signingKey, k3.signingKey)
assertEquals(k1.verifyingKey, k3.verifyingKey)
val k4: UniversalPublicKey = unpack(pack(k1.publicKey))
assertEquals(k1.publicKey, k4)
assertEquals(k1.publicKey.encryptingKey, k4.encryptingKey)
assertEquals(k1.publicKey.verifyingKey, k4.verifyingKey)
val data =
"""We hold these truths to be self-evident, that all men are created equal,
|that they are endowed by their Creator with certain unalienable Rights,
|that among these are Life, Liberty and the pursuit of Happiness."""
.trimMargin()
val kr1 = UniversalRing.from(k1)
val kr2: UniversalRing = UniversalRing.from(k2)
val bytes = Container.encrypt(data, k2.publicKey)
assertNull(Container.decrypt<String>(bytes, kr1))
assertEquals(data, Container.decrypt<String>(bytes, kr2))
}
@Test
fun testEncodedSizes() = runTest {
initCrypto()
val x = SigningSecretKey.new()
// println("key bytes: ${x.keyBytes.size}:\n${x.keyBytes.toDump()}")
val y = BipackEncoder.encode(x)
// println("packed: ${y.size}: ${y.toDump()}")
assertTrue { x.keyBytes.size + 5 > y.size }
assertEquals(x, BipackDecoder.decode<SigningSecretKey>(y))
assertContentEquals(x.keyBytes, BipackDecoder.decode<SigningSecretKey>(y).keyBytes)
}
@Test
fun testEncodedSizes2() = runTest {
initCrypto()
val x = SecretKey.new()
// println("key bytes: ${x.keyBytes.size}:\n${x.keyBytes.toDump()}")
val y = BipackEncoder.encode(x)
// println("packed: ${y.size}: ${y.toDump()}")
assertTrue { x.keyBytes.size + 5 > y.size }
assertEquals(x, BipackDecoder.decode<SecretKey>(y))
assertContentEquals(x.keyBytes, BipackDecoder.decode<SecretKey>(y).keyBytes)
}
@Test
fun testStringRepresentationAndParse() = runTest {
initCrypto()
val k1 = SigningSecretKey.new()
val k2 = k1.verifyingKey
val k3 = SecretKey.new()
val k4 = k3.publicKey
val k5 = UniversalPrivateKey.new()
val k6 = k5.publicKey
assertEquals(32, k2.keyBytes.size)
assertContentEquals(k2.keyBytes, k2.id.id.body)
val k7 = SymmetricKey.new()
val k8 = KDF.Complexity.Interactive.derive("super")
fun testToString(k: UniversalKey) {
val s = k.toString()
val kx = UniversalKey.parseString(s)
assertEquals(kx::class, k::class)
assertContentEquals(k.keyBytes, kx.keyBytes)
assertEquals(k.id, kx.id)
assertEquals(k, kx)
}
fun testAsString(k: UniversalKey) {
val s = k.asString()
val kx = UniversalKey.parseString(s)
assertEquals(kx::class, k::class)
assertContentEquals(k.keyBytes, kx.keyBytes)
assertEquals(k.id, kx.id)
assertEquals(k, kx)
}
testToString(k2)
testToString(k4)
for( i in listOf(k1, k2, k3, k4, k5, k6, k7, k8)) testAsString(i)
val x = VerifyingPublicKey.parse("I1po9Y2I7p2aOxeh4nFyGPm3e0YunBEu1Mo-PmIqP84Evg")
println(x)
} }
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.crypto2.PBKD import net.sergeych.crypto2.PBKD
import net.sergeych.crypto2.initCrypto import net.sergeych.crypto2.initCrypto
@ -33,7 +43,7 @@ class PBKDTest {
assertEquals(i.kdp, kx.id.kdp) assertEquals(i.kdp, kx.id.kdp)
} }
val (y1,y2,y3) = k1.id.kdp!!.kdf.deriveMultiple("foobar", 3) val (y1,y2,y3) = k1.id.kdp!!.kdf.deriveMultipleKeys("foobar", 3)
for( (a,b) in listOf(y1,y2,y3).zip(listOf(k1,k2,k3))) { for( (a,b) in listOf(y1,y2,y3).zip(listOf(k1,k2,k3))) {
assertEquals(a,b) assertEquals(a,b)
assertNotNull(a.id.kdp) assertNotNull(a.id.kdp)

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import net.sergeych.crypto2.initCrypto import net.sergeych.crypto2.initCrypto

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import com.ionspin.kotlin.crypto.util.encodeToUByteArray import com.ionspin.kotlin.crypto.util.encodeToUByteArray
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.toDump import net.sergeych.bintools.toDump
@ -157,14 +167,14 @@ class RingTest {
assertEquals(a, r1.findKey<SecretKey>(a.id)) assertEquals(a, r1.findKey<SecretKey>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a")) assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(b.id)) assertEquals(b, r1.findKey<SigningKey>(b.id))
assertEquals(c, r1.keysById(c.id).first()) assertEquals(c, r1.findById(c.id).first())
r1 = UniversalRing.join(listOf(ra, rb, rc, rd)) r1 = UniversalRing.join(listOf(ra, rb, rc, rd))
assertEquals(a, r1.findKey<SecretKey>(a.id)) assertEquals(a, r1.findKey<SecretKey>(a.id))
assertEquals(a, r1.keyByTag<UniversalKey>("foo_a")) assertEquals(a, r1.keyByTag<UniversalKey>("foo_a"))
assertEquals(b, r1.findKey<SigningKey>(b.id)) assertEquals(b, r1.findKey<SigningKey>(b.id))
assertEquals(c, r1.keysById(c.id).first()) assertEquals(c, r1.findById(c.id).first())
} }
} }

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.encodeToHex
import net.sergeych.crypto2.BinaryId import net.sergeych.crypto2.BinaryId

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import kotlin.test.fail import kotlin.test.fail
inline fun <reified T: Throwable>assertThrows(f: ()->Unit): T { inline fun <reified T: Throwable>assertThrows(f: ()->Unit): T {

View File

@ -1,3 +1,13 @@
/*
* Copyright (c) 2025. Sergey S. Chernov - All Rights Reserved
*
* You may use, distribute and modify this code under the
* terms of the private license, which you must obtain from the author
*
* To obtain the license, contact the author: https://t.me/real_sergeych or email to
* real dot sergeych at gmail.
*/
import net.sergeych.bipack.BipackDecoder import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder import net.sergeych.bipack.BipackEncoder