0.5.8-SNAPSHOT: Multikeys
This commit is contained in:
parent
491f9d47f6
commit
8e652e0421
@ -8,7 +8,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.5.7"
|
version = "0.5.8-SNAPSHOT"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
76
src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt
Normal file
76
src/commonMain/kotlin/net/sergeych/crypto2/ByteChunk.kt
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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 = hex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hex encoded data
|
||||||
|
*/
|
||||||
|
val hex by lazy { data.encodeToHex() }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromHex(hex: String): ByteChunk = ByteChunk(hex.decodeHex().asUByteArray())
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,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 }
|
||||||
|
|
||||||
|
178
src/commonMain/kotlin/net/sergeych/crypto2/Multikey.kt
Normal file
178
src/commonMain/kotlin/net/sergeych/crypto2/Multikey.kt
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
package net.sergeych.crypto2
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* __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(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(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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -27,11 +27,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) } )
|
||||||
@ -61,6 +72,12 @@ 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)
|
||||||
|
|
||||||
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) {
|
||||||
|
@ -33,6 +33,32 @@ class SigningSecretKey(
|
|||||||
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)
|
||||||
|
@ -26,4 +26,32 @@ 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
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -276,4 +276,72 @@ class KeysTest {
|
|||||||
// 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 multiKeyTestSom() = 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) }
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user