From 194fe22afaf963366c002b8a90b1e1485456a2f1 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sat, 28 Sep 2024 00:34:32 +0300 Subject: [PATCH] hash StreamProcessor is now public better docs on BinaryId some sugar --- build.gradle.kts | 2 +- .../kotlin/net/sergeych/crypto2/BinaryId.kt | 57 ++++++++++++++++++- .../kotlin/net/sergeych/crypto2/ByteChunk.kt | 9 ++- .../kotlin/net/sergeych/crypto2/Hash.kt | 7 ++- src/commonTest/kotlin/BinaryIdTest.kt | 16 ++++++ 5 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/commonTest/kotlin/BinaryIdTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index a3623d0..b9c8057 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "net.sergeych" -version = "0.5.8" +version = "0.5.9-SNAPSHOT" repositories { mavenCentral() diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt index 21fa649..6bfbca1 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/BinaryId.kt @@ -5,17 +5,60 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.sergeych.bintools.CRC 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 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 open class BinaryId protected constructor ( + /** + * The packed binary id. Note that serialized version is one byte longer containing + * the size prefix + */ val id: UByteArray, ) : Comparable { + /** + * Bad format (crc does not match) + */ class InvalidException(text: String) : IllegalArgumentException(text) + + /** + * 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) + /** + * magic number (as decoded), `0..255` + */ @Transient val magic: Int = run { if (id.size < 4) throw InvalidException("BinaryId is too short") @@ -30,7 +73,9 @@ open class BinaryId protected constructor ( 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 ) } @@ -41,6 +86,11 @@ open class BinaryId protected constructor ( 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 { if( magic != KeysmagicNumber.defaultAssymmetric.ordinal) throw InvalidException("It is not a veryfing key: magic=$magic, required ${KeysmagicNumber.defaultAssymmetric.ordinal}") @@ -50,6 +100,11 @@ open class BinaryId protected constructor ( 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 { if (other.magic != magic) throw IncomparableException("Mask mismatch (my=$magic their=${other.magic})") val id1 = other.id diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt b/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt index 457a622..8885fe0 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt @@ -70,7 +70,14 @@ class ByteChunk(val data: UByteArray): Comparable { */ 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()) } -} \ No newline at end of file +} + +@Suppress("unused") +fun ByteArray.asChunk() = ByteChunk(toUByteArray()) +fun UByteArray.asChunk(): ByteChunk = ByteChunk(this) \ No newline at end of file diff --git a/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt b/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt index 3eb2e9e..55a62bb 100644 --- a/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt +++ b/src/commonMain/kotlin/net/sergeych/crypto2/Hash.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.Flow import org.kotlincrypto.hash.sha3.SHA3_256 import org.kotlincrypto.hash.sha3.SHA3_384 -private interface StreamProcessor { +interface StreamProcessor { fun update(data: UByteArray) fun final(): UByteArray } @@ -23,7 +23,7 @@ private interface StreamProcessor { @Suppress("unused") enum class Hash( private val direct: ((UByteArray) -> UByteArray)? = null, - private val streamProcessor: () -> StreamProcessor, + val streamProcessor: () -> StreamProcessor, ) { Blake2b( @@ -111,7 +111,6 @@ enum class Hash( for (block in source) sp.update(block) return sp.final() } - } private val defaultSuffix1 = "All lay loads on a willing horse".encodeToUByteArray() @@ -122,6 +121,8 @@ private val defaultSuffix2 = "A stitch in time saves nine".encodeToUByteArray() */ 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 * brute force.collision attack than just [blake2b]. Note that different suffixes provide different diff --git a/src/commonTest/kotlin/BinaryIdTest.kt b/src/commonTest/kotlin/BinaryIdTest.kt new file mode 100644 index 0000000..ac8cd42 --- /dev/null +++ b/src/commonTest/kotlin/BinaryIdTest.kt @@ -0,0 +1,16 @@ +import net.sergeych.crypto2.BinaryId +import kotlin.test.Test +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) + + } +} \ No newline at end of file