diff --git a/.idea/misc.xml b/.idea/misc.xml index e0995c0..7b269bb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,7 +4,7 @@ - + \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/DLogarithm.kt b/src/main/kotlin/org/cryptobiotic/eg/core/DLogarithm.kt index 6c25a09..5fd6234 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/DLogarithm.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/DLogarithm.kt @@ -17,10 +17,10 @@ class DLogarithm(val base: ElementModP) { private val dLogMapping: MutableMap = ConcurrentHashMap() .apply { - this[base.context.ONE_MOD_P] = 0 + this[base.group.ONE_MOD_P] = 0 } - private var dLogMaxElement = base.context.ONE_MOD_P + private var dLogMaxElement = base.group.ONE_MOD_P private var dLogMaxExponent = 0 private val mutex = Mutex() diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt index 5c079f8..c1cdb15 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt @@ -91,13 +91,13 @@ fun List.add(other: List): List ctx.uLongToElementModQ(this.toULong()) } +/** + * Throughout our bignum arithmetic, every operation needs to check that its operands are compatible + * (i.e., that we're not trying to use the test group and the production group interchangeably). + * This will verify that compatibility and throw an `ArithmeticException` if they're not. + */ +fun GroupContext.assertCompatible(other: GroupContext) { + if (!this.isCompatible(other)) { + throw ArithmeticException("incompatible group contexts") + } +} + /** * Verifies that every element has a compatible [GroupContext] and returns the first context. * @@ -217,12 +224,12 @@ fun compatibleContextOrFail(vararg elements: Element): GroupContext { if (elements.isEmpty()) throw IllegalArgumentException("no arguments") - val headContext = elements[0].context + val headContext = elements[0].group // Note: this is comparing the head of the list to itself, which seems inefficient, // but adding something like drop(1) in here would allocate an ArrayList and // entail a bunch of copying overhead. What's here is almost certainly cheaper. - val allCompat = elements.all { it.context.isCompatible(headContext) } + val allCompat = elements.all { it.group.isCompatible(headContext) } if (!allCompat) { throw IllegalArgumentException("incompatible contexts") diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/Nonces.kt b/src/main/kotlin/org/cryptobiotic/eg/core/Nonces.kt index 8c75dc2..849a1f5 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/Nonces.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/Nonces.kt @@ -53,10 +53,7 @@ operator fun Nonces.get(index: Int): ElementModQ = getWithHeaders(index) fun Nonces.getWithHeaders(index: Int, vararg headers: String) = hashFunction(internalSeed, index, *headers).toElementModQ(internalGroup) -/** - * Get an infinite (lazy) sequences of nonces. Equivalent to indexing with [Nonces.get] starting at - * 0. - */ +/** Get an infinite (lazy) sequences of nonces. Equivalent to indexing with [Nonces.get] starting at 1 */ fun Nonces.asSequence(): Sequence = generateSequence(0) { it + 1 }.map { this[it] } /** Gets a list of the desired number (`n`) of nonces. */ diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt b/src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt index 48d8e59..f5397f9 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt @@ -69,17 +69,6 @@ fun fileReadBytes(filename: String): ByteArray = File(filename).readBytes() fun fileReadText(filename: String): String = File(filename).readText() -/** - * Throughout our bignum arithmetic, every operation needs to check that its operands are compatible - * (i.e., that we're not trying to use the test group and the production group interchangeably). - * This will verify that compatibility and throw an `ArithmeticException` if they're not. - */ -fun GroupContext.assertCompatible(other: GroupContext) { - if (!this.isCompatible(other)) { - throw ArithmeticException("incompatible group contexts") - } -} - /** * Convert an unsigned 64-bit long into a big-endian byte array of size 1, 2, 4, or 8 bytes, as * necessary to fit the value. diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt index a352b88..ff07b02 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt @@ -3,8 +3,7 @@ package org.cryptobiotic.eg.core.ecgroup import org.cryptobiotic.eg.core.* import java.util.concurrent.atomic.AtomicInteger -class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP { - override val context: GroupContext = group +class EcElementModP(override val group: EcGroupContext, val ec: VecElementP): ElementModP { override fun acceleratePow(): ElementModP { return EcElementModP(this.group, this.ec.acceleratePow()) @@ -20,7 +19,7 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP override fun div(denominator: ElementModP): ElementModP { require (denominator is EcElementModP) val inv = denominator.ec.inv() - return EcElementModP(group, ec.mul(inv)) + return EcElementModP(this.group, ec.mul(inv)) } /** Validate that this element is a member of the elliptic curve Group.*/ @@ -29,18 +28,18 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP } override fun multInv(): ElementModP { - return EcElementModP(group, ec.inv()) + return EcElementModP(this.group, ec.inv()) } override fun powP(exp: ElementModQ): ElementModP { require (exp is EcElementModQ) - group.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet() - return EcElementModP(group, ec.exp(exp.element)) + this.group.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet() + return EcElementModP(this.group, ec.exp(exp.element)) } override fun times(other: ElementModP): ElementModP { require (other is EcElementModP) - return EcElementModP(group, ec.mul(other.ec)) + return EcElementModP(this.group, ec.mul(other.ec)) } override fun toString(): String { @@ -57,14 +56,14 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP other as EcElementModP - if (group != other.group) return false + if (this.group != other.group) return false if (ec != other.ec) return false return true } override fun hashCode(): Int { - var result = group.hashCode() + var result = this.group.hashCode() result = 31 * result + ec.hashCode() return result } diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt index e17d1dc..b61e68f 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt @@ -4,37 +4,35 @@ import org.cryptobiotic.eg.core.* import org.cryptobiotic.eg.core.Base64.toBase64 import java.math.BigInteger -class EcElementModQ(val group: EcGroupContext, val element: BigInteger): ElementModQ { +// Theres not really any difference with the Integer Group ElementModQ. +class EcElementModQ(override val group: EcGroupContext, val element: BigInteger): ElementModQ { override fun byteArray(): ByteArray = element.toByteArray().normalize(32) - private fun BigInteger.modWrap(): ElementModQ = this.mod(group.vecGroup.order).wrap() - private fun BigInteger.wrap(): ElementModQ = EcElementModQ(group, this) + private fun BigInteger.modWrap(): ElementModQ = this.mod(this@EcElementModQ.group.vecGroup.order).wrap() + private fun BigInteger.wrap(): ElementModQ = EcElementModQ(this@EcElementModQ.group, this) override fun isZero() = element == BigInteger.ZERO - override val context: GroupContext - get() = group + override fun isValidElement() = element >= BigInteger.ZERO && element < this.group.vecGroup.order - override fun isValidElement() = element >= BigInteger.ZERO && element < group.vecGroup.order - - override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(group)) + override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(this.group)) override operator fun plus(other: ElementModQ) = - (this.element + other.getCompat(group)).modWrap() + (this.element + other.getCompat(this.group)).modWrap() override operator fun minus(other: ElementModQ) = this + (-other) override operator fun times(other: ElementModQ) = - (this.element * other.getCompat(group)).modWrap() + (this.element * other.getCompat(this.group)).modWrap() - override fun multInv(): ElementModQ = element.modInverse(group.vecGroup.order).wrap() + override fun multInv(): ElementModQ = element.modInverse(this.group.vecGroup.order).wrap() override operator fun unaryMinus(): ElementModQ = - if (this == group.ZERO_MOD_Q) + if (this == this.group.ZERO_MOD_Q) this else - (group.vecGroup.order - element).wrap() + (this.group.vecGroup.order - element).wrap() override infix operator fun div(denominator: ElementModQ): ElementModQ = this * denominator.multInv() @@ -50,7 +48,7 @@ class EcElementModQ(val group: EcGroupContext, val element: BigInteger): Element override fun toString() = byteArray().toBase64() fun Element.getCompat(other: GroupContext): BigInteger { - context.assertCompatible(other) + group.assertCompatible(other) return when (this) { is EcElementModQ -> this.element else -> throw NotImplementedError("should only be two kinds of elements") diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt index 66a292b..8aa0505 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt @@ -1,6 +1,7 @@ package org.cryptobiotic.eg.core.ecgroup import org.cryptobiotic.eg.core.* +import org.cryptobiotic.eg.core.intgroup.ProductionElementModQ import org.cryptobiotic.eg.core.intgroup.toBigInteger import java.math.BigInteger import java.util.concurrent.atomic.AtomicInteger @@ -31,12 +32,12 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext return EcElementModQ(this, BigInteger(1, b)) } - override fun randomElementModQ(minimum: Int) : ElementModQ { + /** Returns a random number in [2, Q). */ + override fun randomElementModQ() : ElementModQ { val b = randomBytes(MAX_BYTES_Q) - val bigMinimum = if (minimum <= 0) BigInteger.ZERO else minimum.toBigInteger() val tmp = b.toBigInteger().mod(vecGroup.order) - val big = if (tmp < bigMinimum) tmp + bigMinimum else tmp - return EcElementModQ(this, big) + val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp + return EcElementModQ(this, tmp2) } override fun randomElementModP() = EcElementModP(this, vecGroup.randomElement()) diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/RFC9380.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/RFC9380.kt new file mode 100644 index 0000000..e2fed9e --- /dev/null +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/RFC9380.kt @@ -0,0 +1,106 @@ +package org.cryptobiotic.eg.core.ecgroup + +// import jdk.vm.ci.code.CodeUtil.log2 +import kotlin.math.ceil + +// https://www.rfc-editor.org/rfc/rfc9380.pdf + +class RFC9380(val DST: ByteArray, p: Int, k: Int, val m: Int) { + // val L = ceil((ceil(log2(p)) + k) / 8) + + // hash_to_field(msg, count) + //Parameters: + //- DST, a domain separation tag (see Section 3.1). + //- F, a finite field of characteristic p and order q = p^m. + //- p, the characteristic of F (see immediately above). + //- m, the extension degree of F, m >= 1 (see immediately above). + //- L = ceil((ceil(log2(p)) + k) / 8), where k is the security + // parameter of the suite (e.g., k = 128). + // + //- expand_message, a function that expands a byte string and + // domain separation tag into a uniformly random byte string (see Section 5.3). + //Input: + //- msg, a byte string containing the message to hash. + //- count, the number of elements of F to output. + //Output: + //- (u_0, ..., u_(count - 1)), a list of field elements. + //Steps: + //1. len_in_bytes = count * m * L + //2. uniform_bytes = expand_message(msg, DST, len_in_bytes) + //3. for i in (0, ..., count - 1): + //4. for j in (0, ..., m - 1): + //5. elm_offset = L * (j + i * m) + //6. tv = substr(uniform_bytes, elm_offset, L) + //7. e_j = OS2IP(tv) mod p + //8. u_i = (e_0, ..., e_(m - 1)) + //9. return (u_0, ..., u_(count - 1)) + + /* + fun hash_to_field(msg: ByteArray, count: Int) : ByteArray { + //Steps: + val len_in_bytes = count * m * L + val uniform_bytes = expand_message(msg, DST, len_in_bytes) + //3. for i in (0, ..., count - 1): + //4. for j in (0, ..., m - 1): + //5. elm_offset = L * (j + i * m) + //6. tv = substr(uniform_bytes, elm_offset, L) + //7. e_j = OS2IP(tv) mod p + //8. u_i = (e_0, ..., e_(m - 1)) + //9. return (u_0, ..., u_(count - 1)) + } + + */ + +// expand_message_xmd(msg, DST, len_in_bytes) +// Parameters: +// - H, a hash function (see requirements above). +// - b_in_bytes, b / 8 for b the output size of H in bits. +// For example, for b = 256, b_in_bytes = 32. +// - s_in_bytes, the input block size of H, measured in bytes (see +// discussion above). For example, for SHA-256, s_in_bytes = 64. + +// Input: +// - msg, a byte string. +// - DST, a byte string of at most 255 bytes. +// See below for information on using longer DSTs. +// - len_in_bytes, the length of the requested output in bytes, +// not greater than the lesser of (255 * b_in_bytes) or 2^16-1. +// Output: +// - uniform_bytes, a byte string. + +// Steps: +// 1. ell = ceil(len_in_bytes / b_in_bytes) +// 2. ABORT if ell > 255 or len_in_bytes > 65535 or len(DST) > 255 +// 3. DST_prime = DST || I2OSP(len(DST), 1) +// 4. Z_pad = I2OSP(0, s_in_bytes) +// 5. l_i_b_str = I2OSP(len_in_bytes, 2) +// 6. msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime +// 7. b_0 = H(msg_prime) +// 8. b_1 = H(b_0 || I2OSP(1, 1) || DST_prime) +// 9. for i in (2, ..., ell): +// 10. b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) +// 11. uniform_bytes = b_1 || ... || b_ell +// 12. return substr(uniform_bytes, 0, len_in_bytes) + + /* + fun expand_message(msg: ByteArray, DST: ByteArray, len_in_bytes: Int) { + val ell = ceil(len_in_bytes / b_in_bytes) + require (ell < 255 && len_in_bytes < 65535 && DST.size < 255) + + val DST_prime = DST || I2OSP(len(DST), 1) + val Z_pad = I2OSP(0, s_in_bytes) + val l_i_b_str = I2OSP(len_in_bytes, 2) + val msg_prime = Z_pad || msg || l_i_b_str || I2OSP(0, 1) || DST_prime + val b_0 = H(msg_prime) + val b_1 = H(b_0 || I2OSP(1, 1) || DST_prime) + for (i in (2..ell)) { + val b_i = H(strxor(b_0, b_(i - 1)) || I2OSP(i, 1) || DST_prime) + } + val uniform_bytes = b_1 || ... || b_ell + return substr(uniform_bytes, 0, len_in_bytes) + } + + */ + + +} \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt index b743538..4cc9ee9 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt @@ -355,6 +355,14 @@ open class VecGroup( return x == MINUS_ONE && y == MINUS_ONE } + /** + * Returns the Jacobi symbol of this instance modulo the input. + * This is an implementation of the binary Jacobi-symbol algorithm. + * + * @param value Integer to test. + * @param modulus An odd modulus. + * @return Jacobi symbol of this instance modulo the input. + */ fun jacobiSymbol(value: BigInteger, modulus: BigInteger): Int { var a = value var n = modulus diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt index cec3f56..dfac810 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt @@ -89,11 +89,7 @@ class ProductionGroupContext( return (ctx is ProductionGroupContext) && productionMode == ctx.productionMode } - /** - * Returns a random number in [2, P). Promises to use a - * "secure" random number generator, such that the results are suitable for use as cryptographic keys. - * @throws IllegalArgumentException if the minimum is negative - */ + /** Returns a random number in [2, P). */ override fun randomElementModP(): ElementModP { val b = randomBytes(MAX_BYTES_P) val tmp = b.toBigInteger().mod(p) @@ -109,11 +105,11 @@ class ProductionGroupContext( null } - override fun randomElementModQ(minimum: Int) : ElementModQ { + /** Returns a random number in [2, Q). */ + override fun randomElementModQ() : ElementModQ { val b = randomBytes(MAX_BYTES_Q) - val bigMinimum = if (minimum <= 0) BigInteger.ZERO else minimum.toBigInteger() val tmp = b.toBigInteger().mod(q) - val tmp2 = if (tmp < bigMinimum) tmp + bigMinimum else tmp + val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp return ProductionElementModQ(tmp2, this) } @@ -159,7 +155,7 @@ class ProductionGroupContext( } private fun Element.getCompat(other: ProductionGroupContext): BigInteger { - context.assertCompatible(other) + group.assertCompatible(other) return when (this) { is ProductionElementModP -> this.element is ProductionElementModQ -> this.element @@ -167,39 +163,35 @@ private fun Element.getCompat(other: ProductionGroupContext): BigInteger { } } -class ProductionElementModQ(internal val element: BigInteger, val groupContext: ProductionGroupContext): ElementModQ, +class ProductionElementModQ(internal val element: BigInteger, override val group: ProductionGroupContext): ElementModQ, Element, Comparable { override fun byteArray(): ByteArray = element.toByteArray().normalize(32) - private fun BigInteger.modWrap(): ElementModQ = this.mod(groupContext.q).wrap() - private fun BigInteger.wrap(): ElementModQ = ProductionElementModQ(this, groupContext) - - override val context: GroupContext - get() = groupContext + private fun BigInteger.modWrap(): ElementModQ = this.mod(this@ProductionElementModQ.group.q).wrap() + private fun BigInteger.wrap(): ElementModQ = ProductionElementModQ(this, this@ProductionElementModQ.group) override fun isZero() = element == BigInteger.ZERO + override fun isValidElement() = element >= BigInteger.ZERO && element < this.group.q - override fun isValidElement() = element >= BigInteger.ZERO && element < groupContext.q - - override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(groupContext)) + override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(this.group)) override operator fun plus(other: ElementModQ) = - (this.element + other.getCompat(groupContext)).modWrap() + (this.element + other.getCompat(this.group)).modWrap() override operator fun minus(other: ElementModQ) = this + (-other) override operator fun times(other: ElementModQ) = - (this.element * other.getCompat(groupContext)).modWrap() + (this.element * other.getCompat(this.group)).modWrap() - override fun multInv(): ElementModQ = element.modInverse(groupContext.q).wrap() + override fun multInv(): ElementModQ = element.modInverse(this.group.q).wrap() override operator fun unaryMinus(): ElementModQ = - if (this == groupContext.zeroModQ) + if (this == this.group.zeroModQ) this else - (groupContext.q - element).wrap() + (this.group.q - element).wrap() override infix operator fun div(denominator: ElementModQ): ElementModQ = this * denominator.multInv() @@ -223,7 +215,7 @@ open class ProductionElementModP(internal val element: BigInteger, val groupCont private fun BigInteger.modWrap(): ElementModP = this.mod(groupContext.p).wrap() private fun BigInteger.wrap(): ElementModP = ProductionElementModP(this, groupContext) - override val context: GroupContext + override val group: GroupContext get() = groupContext override operator fun compareTo(other: ElementModP): Int = element.compareTo(other.getCompat(groupContext)) diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroups.kt b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroups.kt index da6b585..85b7757 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroups.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroups.kt @@ -103,76 +103,3 @@ fun productionIntGroup(acceleration: PowRadixOption = PowRadixOption.LOW_MEMORY_ ProductionMode.Mode4096 -> productionGroups4096[acceleration] ?: throw Error("can't happen") ProductionMode.Mode3072 -> productionGroups3072[acceleration] ?: throw Error("can't happen") } - -// -// /* -// * Verifies that every element has a compatible [GroupContext] and returns the first context. -// * -// * @throws IllegalArgumentException if there's an incompatibility. -// fun compatibleContextOrFail(vararg elements: Element): GroupContext { -// // Engineering note: If this method fails, that means we have a bug in our program. -// // We should never allow incompatible data to be processed. We should catch -// // this when we're loading the data in the first place. -// -// if (elements.isEmpty()) throw IllegalArgumentException("no arguments") -// -// val headContext = elements[0].context -// -// // Note: this is comparing the head of the list to itself, which seems inefficient, -// // but adding something like drop(1) in here would allocate an ArrayList and -// // entail a bunch of copying overhead. What's here is almost certainly cheaper. -// val allCompat = elements.all { it.context.isCompatible(headContext) } -// -// if (!allCompat) { -// throw IllegalArgumentException("incompatible contexts") -// } -// -// return headContext -// } -// -// * Given an element x for which there exists an e, such that g^e = x, this will find e, -// * so long as e is less than [maxResult], which if unspecified defaults to a platform-specific -// * value designed not to consume too much memory (perhaps 10 million). This will consume O(e) -// * time, the first time, after which the results are memoized for all values between 0 and e, -// * for better future performance. -// * -// * If the result is not found, `null` is returned. -// fun ElementModP.dLogG(maxResult: Int = -1): Int? = context.dLogG(this, maxResult) -// -// * Converts from an external [ElectionConstants] to an internal [GroupContext]. Note the optional -// * `acceleration` parameter, to specify the speed versus memory tradeoff for subsequent computation. -// * See [PowRadixOption] for details. Note that this function can return `null`, which indicates that -// * the [ElectionConstants] were incompatible with this particular library. -// * -// fun ElectionConstants.toGroupContext( -// acceleration: PowRadixOption = PowRadixOption.LOW_MEMORY_USE -// ) : GroupContext? { -// val group4096 = productionGroup(acceleration = acceleration, mode = ProductionMode.Mode4096) -// val group3072 = productionGroup(acceleration = acceleration, mode = ProductionMode.Mode3072) -// -// return when { -// group4096.isCompatible(this) -> group4096 -// group3072.isCompatible(this) -> group3072 -// else -> { -// logger.error { -// "unrecognized cryptographic parameters; this election was encrypted using a " + -// "library incompatible with this one: $this" -// } -// null -// } -// } -// } -// -// /** -// * Computes the sum of the given elements, mod q; this can be faster than using the addition -// * operation for large numbers of inputs by potentially reusing scratch-space memory. -// */ -// fun GroupContext.addQ(vararg elements: ElementModQ) = elements.asIterable().addQ() -// -// /** -// * Computes the product of the given elements, mod p; this can be faster than using the -// * multiplication operation for large numbers of inputs by potentially reusing scratch-space memory. -// */ -// fun GroupContext.multP(vararg elements: ElementModP) = elements.asIterable().multP() -// -// */ \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadix.kt b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadix.kt index 28a88cd..b514c43 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadix.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadix.kt @@ -71,7 +71,7 @@ class PowRadix(val basis: ProductionElementModP, val acceleration: PowRadixOptio init { val k = acceleration.numBits val mBasis = basis.toMontgomeryElementModP() - montgomeryOne = (basis.context.ONE_MOD_P as ProductionElementModP).toMontgomeryElementModP() + montgomeryOne = (basis.group.ONE_MOD_P as ProductionElementModP).toMontgomeryElementModP() if (k == 0) { tableLength = 0 @@ -101,7 +101,7 @@ class PowRadix(val basis: ProductionElementModP, val acceleration: PowRadixOptio } fun pow(e: ElementModQ): ElementModP { - basis.context.assertCompatible(e.context) + basis.group.assertCompatible(e.group) return if (acceleration.numBits == 0) basis powP e else { val slices = e.byteArray().kBitsPerSlice(acceleration, tableLength) diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt index 27eaf09..276918e 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt @@ -33,7 +33,7 @@ data class DecryptingTrustee( texts: List, ): PartialDecryptions { - val seed = group.randomElementModQ(2) // random value u in Zq + val seed = group.randomElementModQ() // random value u in Zq val batchId = randomInt() runBlocking { mutex.withLock { diff --git a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt index 0798fba..5fabd9e 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt @@ -202,10 +202,10 @@ open class KeyCeremonyTrustee( open fun shareEncryption( Pil: ElementModQ, other: PublicKeys, - nonce: ElementModQ = group.randomElementModQ(minimum = 2) + nonce: ElementModQ = group.randomElementModQ() ): HashedElGamalCiphertext { - val hp = Pil.context.constants.parameterBaseHash + val hp = Pil.group.constants.parameterBaseHash val i = xCoordinate val l = other.guardianXCoordinate diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ChaumPedersenTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ChaumPedersenTest.kt index 869cf1d..2f8f164 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ChaumPedersenTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ChaumPedersenTest.kt @@ -338,12 +338,12 @@ class ChaumPedersenTest { fun testAccumDifferentNonces(context: GroupContext) { val constant = 42 - val contestNonce = context.randomElementModQ(2) + val contestNonce = context.randomElementModQ() val hashHeader = UInt256.random() - val keyPair = elGamalKeyPairFromSecret(context.randomElementModQ(2)) + val keyPair = elGamalKeyPairFromSecret(context.randomElementModQ()) val publicKey = keyPair.publicKey - val randomQ = context.randomElementModQ(2) + val randomQ = context.randomElementModQ() val nonceSequence = Nonces(randomQ, contestNonce) val vote0 = 0.encrypt(publicKey, nonceSequence[0]) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalEncryptionTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalEncryptionTest.kt index ae1e557..f7c94da 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalEncryptionTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalEncryptionTest.kt @@ -83,7 +83,7 @@ private fun testEncryption(name: String, group: GroupContext) = wordSpec { smallInts() ) { keypair, message -> val encryption = message.encrypt(keypair) - val reencryption = encryption.reencrypt(keypair.publicKey, group.randomElementModQ(minimum = 1)) + val reencryption = encryption.reencrypt(keypair.publicKey, group.randomElementModQ()) val decryption = reencryption.decrypt(keypair) message shouldBe decryption } @@ -100,7 +100,7 @@ private fun testEncryption(name: String, group: GroupContext) = wordSpec { elGamalKeypairs(group), smallInts() ) { keypair, message -> val org = message.encrypt(keypair) - val extra: ElementModQ = group.randomElementModQ(minimum = 1) + val extra: ElementModQ = group.randomElementModQ() val extraEncryption = ElGamalCiphertext(org.pad * group.gPowP(extra), org.data * (keypair.publicKey powP extra)) val decryption = extraEncryption.decrypt(keypair) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalKeysTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalKeysTest.kt index 857a050..443cde7 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalKeysTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ElGamalKeysTest.kt @@ -17,7 +17,7 @@ class ElGamalKeysTest : WordSpec({ private fun testKeys(name: String, group: GroupContext) = wordSpec { name should { "have correct public key using elGamalKeyPairFromSecret" { - val secret = group.randomElementModQ(2) + val secret = group.randomElementModQ() val keypair = elGamalKeyPairFromSecret(secret) keypair.secretKey shouldBe secret keypair.publicKey shouldBe group.gPowP(secret) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/TestRandom.kt b/src/test/kotlin/org/cryptobiotic/eg/core/TestRandom.kt index cd185b9..ed331e4 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/TestRandom.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/TestRandom.kt @@ -1,17 +1,15 @@ package org.cryptobiotic.eg.core import org.junit.jupiter.api.Test +import java.security.DrbgParameters import java.security.SecureRandom import java.security.Security import java.util.* +import kotlin.test.assertFalse +import kotlin.test.assertTrue -class TestRandom { - @Test - fun testRandom() { - val rng = Random() - println("Random $rng") - } +class TestRandom { @Test fun testSecureRandom() { @@ -29,4 +27,62 @@ class TestRandom { algorithms.forEach { println(" $it") } } + @Test + fun showDRBG() { + val rng = SecureRandom.getInstance("DRBG") + println("SecureRandom.getInstanceStrong") + println(" algo=${rng.algorithm}") + println(" params=${rng.parameters}") + println(" provider=${rng.provider}") + println(" rng=${rng}") + println(" class=${rng.javaClass.getName()}") + } + + @Test + fun testDRBG() { + val n = 1 + val r1 = SecureRandom.getInstance("DRBG") + val r2 = SecureRandom.getInstance("DRBG") + assertFalse(r1 === r2) + + r1.setSeed(1234567L) + r2.setSeed(1234567L) + + val ba1 = r1.fill(32, n) + val ba2 = r2.fill(32, n) + + // not deterministic + repeat (n) { + assertFalse(ba1[it].contentEquals(ba2[it])) + } + } + + @Test + fun testRandom() { + val n = 1000 + val r1 = Random() + val r2 = Random() + assertFalse(r1 === r2) + + r1.setSeed(1234567L) + r2.setSeed(1234567L) + + val ba1 = r1.fill(32, n) + val ba2 = r2.fill(32, n) + + repeat (n) { + assertTrue(ba1[it].contentEquals(ba2[it])) + } + } + + fun Random.fill(size: Int, n: Int): List { + val result = mutableListOf() + repeat (n) { + val ba = ByteArray(size) + this.nextBytes(ba) + result.add(ba) + } + return result + } + } \ No newline at end of file diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestJacobi.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestJacobi.kt new file mode 100644 index 0000000..5049317 --- /dev/null +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestJacobi.kt @@ -0,0 +1,43 @@ +package org.cryptobiotic.eg.core.ecgroup + +import org.cryptobiotic.eg.core.ecgroup.VecGroup.Companion.jacobiSymbol +import org.cryptobiotic.eg.core.randomBytes +import java.math.BigInteger +import kotlin.test.Test +import kotlin.test.assertEquals + +class TestJacobi { + + @Test + fun testJacobiJava() { + val group = EcGroupContext("P-256", false) + testJacobi(group.vecGroup) + } + + @Test + fun testJacobiNative() { + if (!VecGroups.hasNativeLibrary()) return + + val group = EcGroupContext("P-256", true) + testJacobi(group.vecGroup) + } + + fun testJacobi(vgroup: VecGroup) { + val pM1over2= (vgroup.primeModulus - BigInteger.ONE) / BigInteger.TWO + + val ntrials = 1000 + var countTrue = 0 + repeat(ntrials) { + val x = BigInteger(1, randomBytes(vgroup.pbyteLength)) + val fx = vgroup.equationf(x) + val isJacobiOne = jacobiSymbol(fx, vgroup.primeModulus) == 1 + if (isJacobiOne) countTrue++ + + // presumably its equivalent to y^((p-1)/2) == 1 as described in \cite{Haines20} + val testHaines = fx.modPow(pM1over2, vgroup.primeModulus) == BigInteger.ONE + // println("isJacobiOne = $isJacobiOne testHaines=$testHaines") + assertEquals(isJacobiOne, testHaines) + } + println(" ${vgroup.javaClass} isJacobiOne= $countTrue / $ntrials") + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt index c3ec4fc..d456139 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt @@ -24,7 +24,7 @@ internal val tinyGroupContext = fun tinyGroup(): GroupContext = tinyGroupContext internal fun Element.getCompat(other: GroupContext): UInt { - context.assertCompatible(other) + group.assertCompatible(other) return when (this) { is TinyElementModP -> this.element is TinyElementModQ -> this.element @@ -111,11 +111,10 @@ internal class TinyGroupContext( } } - override fun randomElementModQ(minimum: Int) : ElementModQ { + override fun randomElementModQ() : ElementModQ { val b = randomBytes(MAX_BYTES_Q) - val useMinimum = if (minimum <= 0) 0U else minimum.toUInt() val u32 = b.toUIntMod(q) - val result = if (u32 < useMinimum) u32 + useMinimum else u32 + val result = if (u32 < 2U) u32 + 2U else u32 return uIntToElementModQ(result) } @@ -216,7 +215,7 @@ internal class TinyElementModP(val element: UInt, val groupContext: TinyGroupCon override fun compareTo(other: ElementModP): Int = element.compareTo(other.getCompat(groupContext)) - override val context: GroupContext + override val group: GroupContext get() = groupContext // fun inBounds(): Boolean = element < groupContext.p @@ -291,7 +290,7 @@ internal class TinyElementModQ(val element: UInt, val groupContext: TinyGroupCon override fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(groupContext)) - override val context: GroupContext + override val group: GroupContext get() = groupContext override fun isValidElement(): Boolean = element < groupContext.q diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt index 1e6c64f..63865aa 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt @@ -66,7 +66,7 @@ fun encryptDecrypt( val missing = trustees.filter {!present.contains(it.xCoordinate())}.map { it.id() } println("present $present, missing $missing") val vote = 42 - val evote = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) + val evote = vote.encrypt(publicKey, group.randomElementModQ()) val available = trustees.filter {present.contains(it.xCoordinate())} val lagrangeCoefficients = available.associate { it.id() to computeLagrangeCoefficient(group, it.xCoordinate(), present) } diff --git a/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt b/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt index cb8bd03..68ce56e 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt @@ -186,7 +186,7 @@ internal fun runComplete( ) { if (show) println("===================================================================") val qbar = 4242U.toUInt256() - val secret = group.randomElementModQ(minimum = 1) + val secret = group.randomElementModQ() val publicKey = ElGamalPublicKey(group.gPowP(secret)) val primaryNonce = UInt256.random() diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt index 02b8a5b..98d280c 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt @@ -64,7 +64,7 @@ class KeyCeremonyJsonTest { Arb.string(minSize = 3), Arb.string(minSize = 3), ) { id, owner, secretShareFor, -> - val kshare = KeyShare(id, owner, secretShareFor, group.randomElementModQ(2)) + val kshare = KeyShare(id, owner, secretShareFor, group.randomElementModQ()) assertEquals(kshare, kshare.publishJson().import(group)) } } diff --git a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt index 28f471e..d8b15d2 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt @@ -135,7 +135,7 @@ fun testDoerreDecrypt(group: GroupContext, val missing = trustees.filter {!present.contains(it.xCoordinate())}.map { it.id } println("present $present, missing $missing") val vote = 42 - val evote = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) + val evote = vote.encrypt(publicKey, group.randomElementModQ()) val available = trustees.filter {present.contains(it.xCoordinate())} val lagrangeCoefficients = available.associate { it.id to computeLagrangeCoefficient(group, it.xCoordinate, present) } diff --git a/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt b/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt index c9532c4..e44e379 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt @@ -19,10 +19,10 @@ class TestWorkflowEncryptDecrypt { fun singleTrusteeZero() { runTest { val group = productionGroup() - val secret = group.randomElementModQ(minimum = 1) + val secret = group.randomElementModQ() val publicKey = ElGamalPublicKey(group.gPowP(secret)) val keypair = ElGamalKeypair(ElGamalSecretKey(secret), publicKey) - val nonce = group.randomElementModQ(minimum = 1) + val nonce = group.randomElementModQ() // accumulate random sequence of 1 and 0 val vote = 0 @@ -44,10 +44,10 @@ class TestWorkflowEncryptDecrypt { fun singleTrusteeOne() { runTest { val group = productionGroup() - val secret = group.randomElementModQ(minimum = 1) + val secret = group.randomElementModQ() val publicKey = ElGamalPublicKey(group.gPowP(secret)) val keypair = ElGamalKeypair(ElGamalSecretKey(secret), publicKey) - val nonce = group.randomElementModQ(minimum = 1) + val nonce = group.randomElementModQ() // acumulate random sequence of 1 and 0 val vote = 1 @@ -68,15 +68,15 @@ class TestWorkflowEncryptDecrypt { fun singleTrusteeTally() { runTest { val group = productionGroup() - val secret = group.randomElementModQ(minimum = 1) + val secret = group.randomElementModQ() val publicKey = ElGamalPublicKey(group.gPowP(secret)) assertEquals(group.gPowP(secret), publicKey.key) val keypair = ElGamalKeypair(ElGamalSecretKey(secret), publicKey) val vote = 1 - val evote1 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) - val evote2 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) - val evote3 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) + val evote1 = vote.encrypt(publicKey, group.randomElementModQ()) + val evote2 = vote.encrypt(publicKey, group.randomElementModQ()) + val evote3 = vote.encrypt(publicKey, group.randomElementModQ()) val accum = listOf(evote1, evote2, evote3) val eAccum = accum.encryptedSum()?: 0.encrypt(publicKey) @@ -102,9 +102,9 @@ class TestWorkflowEncryptDecrypt { val publicKey = ElGamalPublicKey( group.multP(pkeys) ) val vote = 1 - val evote1 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) - val evote2 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) - val evote3 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) + val evote1 = vote.encrypt(publicKey, group.randomElementModQ()) + val evote2 = vote.encrypt(publicKey, group.randomElementModQ()) + val evote3 = vote.encrypt(publicKey, group.randomElementModQ()) // tally val accum = listOf(evote1, evote2, evote3)