Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

possible fix for the ULongArray issue and for mode_t portability #143

Merged
merged 6 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ cmake_minimum_required(VERSION 3.21)
# set(CMAKE_C_COMPILER "/usr/local/bin/gcc-11" CACHE STRING "gcc compiler" FORCE)

project(hacl)
include("cmake/OptimizeForArchitecture.cmake")

OptimizeForArchitecture()
# This doesn't work at all on M1 Macs
# include("cmake/OptimizeForArchitecture.cmake")
# OptimizeForArchitecture()

# This line requests that we compile universal binaries.
# More details:
# https://stackoverflow.com/a/65811061
# https://stackoverflow.com/questions/67490441/cmake-universal-binary-arch-depending-compile-options
# CMAKE_OSX_ARCHITECTURES=arm64;x86_64
set (CMAKE_OSX_ARCHITECTURES arm64 x86_64)

ADD_LIBRARY(hacl STATIC
libhacl/src/Hacl_Bignum.c
Expand Down
20 changes: 10 additions & 10 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ buildscript {
}

plugins {
kotlin("multiplatform") version "1.7.10"
kotlin("multiplatform") version "1.7.20-Beta"

// cross-platform serialization support
kotlin("plugin.serialization") version "1.7.10"
kotlin("plugin.serialization") version "1.7.20-Beta"

// https://github.com/hovinen/kotlin-auto-formatter
// Creates a `formatKotlin` Gradle action that seems to be reliable.
Expand All @@ -24,7 +24,7 @@ plugins {
group = "electionguard-kotlin-multiplatform"
version = "1.0-SNAPSHOT"

val kotlinVersion by extra("1.7.10")
val kotlinVersion by extra("1.7.20-Beta")
val pbandkVersion by extra("0.14.1")

repositories {
Expand Down Expand Up @@ -95,8 +95,8 @@ kotlin {
val commonMain by
getting {
dependencies {
implementation(kotlin("stdlib-common", "$kotlinVersion"))
implementation(kotlin("stdlib", "$kotlinVersion"))
implementation(kotlin("stdlib-common", kotlinVersion))
implementation(kotlin("stdlib", kotlinVersion))

// JSON serialization and DSL
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
Expand Down Expand Up @@ -128,8 +128,8 @@ kotlin {
val commonTest by
getting {
dependencies {
implementation(kotlin("test-common", "$kotlinVersion"))
implementation(kotlin("test-annotations-common", "$kotlinVersion"))
implementation(kotlin("test-common", kotlinVersion))
implementation(kotlin("test-annotations-common", kotlinVersion))

// runTest() for running suspend functions in tests
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
Expand All @@ -141,7 +141,7 @@ kotlin {
val jvmMain by
getting {
dependencies {
implementation(kotlin("stdlib-jdk8", "$kotlinVersion"))
implementation(kotlin("stdlib-jdk8", kotlinVersion))

// Progress bars
implementation("me.tongfei:progressbar:0.9.3")
Expand All @@ -157,7 +157,7 @@ kotlin {
dependencies {
// Unclear if we really need all the extra features of JUnit5, but it would
// at least be handy if we could get its parallel test runner to work.
implementation(kotlin("test-junit5", "$kotlinVersion"))
implementation(kotlin("test-junit5", kotlinVersion))
}
}
val nativeMain by getting { dependencies {} }
Expand Down Expand Up @@ -226,7 +226,7 @@ configurations.forEach {
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>()
.configureEach { kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" }
.configureEach { kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" }

publishing {
repositories {
Expand Down
4 changes: 3 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
kotlin.code.style=official
kotlin.js.generate.executable.default=false
kotlin.mpp.stability.nowarn=true
kotlin.native.binary.memoryModel=experimental
kotlin.native.binary.memoryModel=experimental
org.gradle.jvmargs=-Xmx6g
kotlin.native.cacheKind.macosArm64=none
96 changes: 50 additions & 46 deletions src/nativeMain/kotlin/electionguard/core/Group.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
@file:OptIn(ExperimentalUnsignedTypes::class)
@file:Suppress("EXPERIMENTAL_IS_NOT_ENABLED") // fix IntelliJ confusion

package electionguard.core

import electionguard.core.Base64.fromSafeBase64
Expand Down Expand Up @@ -51,8 +48,12 @@ actual fun productionGroup(acceleration: PowRadixOption, mode: ProductionMode) :
ProductionMode.Mode3072 -> productionGroups3072[acceleration] ?: throw Error("can't happen")
}

typealias HaclBignumP = ULongArray
typealias HaclBignumQ = ULongArray
// We use these aliases rather than the concrete types, so changes are simpler.
// In particular, we used to use ULongArray rather than LongArray, but
// this caused some weird linking problems. Now it's all in one place.
typealias HaclBignumLongArray = LongArray
typealias HaclBignumP = LongArray
typealias HaclBignumQ = LongArray

internal const val HaclBignumQ_LongWords = 4
internal const val HaclBignumQ_Bytes = HaclBignumQ_LongWords * 8
Expand All @@ -62,45 +63,45 @@ internal fun newZeroBignumP(mode: ProductionMode) = HaclBignumP(mode.numLongWord
internal fun newZeroBignumQ() = HaclBignumQ(HaclBignumQ_LongWords)

// Helper functions that make it less awful to go back and forth from Kotlin to C interaction.
// What's going on here: before we can pass a pointer from a Kotlin-managed ULongArray into
// What's going on here: before we can pass a pointer from a Kotlin-managed LongArray into
// a C function, we need to "pin" it in memory, guaranteeing that a hypothetical copying
// garbage collector won't come along and relocate that buffer while we were sending a
// raw pointer to it into the C library. That's what usePinned() is all about. At least in
// how we're using HACL, we're relying on these functions being stateless. We know that
// whatever we pass in won't be retained, so once the call returns, the pinning is no
// longer necessary.

internal inline fun <T> nativeElems(a: ULongArray,
b: ULongArray,
c: ULongArray,
d: ULongArray,
e: ULongArray,
internal inline fun <T> nativeElems(a: HaclBignumLongArray,
b: HaclBignumLongArray,
c: HaclBignumLongArray,
d: HaclBignumLongArray,
e: HaclBignumLongArray,
f: (ap: CPointer<ULongVar>, bp: CPointer<ULongVar>, cp: CPointer<ULongVar>, dp: CPointer<ULongVar>, ep: CPointer<ULongVar>) -> T): T =
a.useNative { ap -> b.useNative { bp -> c.useNative { cp -> d.useNative { dp -> e.useNative { ep -> f(ap, bp, cp, dp, ep) } } } } }

internal inline fun <T> nativeElems(a: ULongArray,
b: ULongArray,
c: ULongArray,
d: ULongArray,
internal inline fun <T> nativeElems(a: HaclBignumLongArray,
b: HaclBignumLongArray,
c: HaclBignumLongArray,
d: HaclBignumLongArray,
f: (ap: CPointer<ULongVar>, bp: CPointer<ULongVar>, cp: CPointer<ULongVar>, dp: CPointer<ULongVar>) -> T): T =
a.useNative { ap -> b.useNative { bp -> c.useNative { cp -> d.useNative { dp -> f(ap, bp, cp, dp) } } } }

internal inline fun <T> nativeElems(a: ULongArray,
b: ULongArray,
c: ULongArray,
internal inline fun <T> nativeElems(a: HaclBignumLongArray,
b: HaclBignumLongArray,
c: HaclBignumLongArray,
f: (ap: CPointer<ULongVar>, bp: CPointer<ULongVar>, cp: CPointer<ULongVar>) -> T): T =
a.useNative { ap -> b.useNative { bp -> c.useNative { cp -> f(ap, bp, cp) } } }

internal inline fun <T> nativeElems(a: ULongArray,
b: ULongArray,
internal inline fun <T> nativeElems(a: HaclBignumLongArray,
b: HaclBignumLongArray,
f: (ap: CPointer<ULongVar>, bp: CPointer<ULongVar>) -> T): T =
a.useNative { ap -> b.useNative { bp -> f(ap, bp) } }

internal inline fun <T> nativeElems(a: ULongArray,
internal inline fun <T> nativeElems(a: HaclBignumLongArray,
f: (ap: CPointer<ULongVar>) -> T): T =
a.useNative { ap -> f(ap) }

internal inline fun <T> ULongArray.useNative(f: (CPointer<ULongVar>) -> T): T =
internal inline fun <T> HaclBignumLongArray.useNative(f: (CPointer<ULongVar>) -> T): T =
usePinned { ptr ->
f(ptr.addressOf(0).reinterpret())
}
Expand All @@ -115,7 +116,7 @@ internal fun UInt.toHaclBignumQ(): HaclBignumQ = toByteArray().toHaclBignumQ()

internal fun UInt.toHaclBignumP(mode: ProductionMode): HaclBignumP = toByteArray().toHaclBignumP(mode = mode)

/** Convert an array of bytes, in big-endian format, to a HaclBignum256. */
/** Convert an array of bytes, in big-endian format, to a HaclBignumQ. */
internal fun ByteArray.toHaclBignumQ(doubleMemory: Boolean = false): HaclBignumQ {
// See detailed comments in ByteArray.toHaclBignumP() for details on
// what's going on here.
Expand All @@ -135,16 +136,14 @@ internal fun ByteArray.toHaclBignumQ(doubleMemory: Boolean = false): HaclBignumQ
}
}
bytesToUse.useNative { bytes ->
val tmp: CPointer<ULongVar>? =
val tmp: CPointer<uint64_tVar> =
Hacl_Bignum256_new_bn_from_bytes_be(HaclBignumQ_Bytes.convert(), bytes)
if (tmp == null) {
throw OutOfMemoryError()
}
?: throw OutOfMemoryError()

// make a copy to Kotlin-managed memory and free the Hacl-managed original
val result = ULongArray((if (doubleMemory) 2 else 1) * HaclBignumQ_LongWords) {
val result = HaclBignumLongArray((if (doubleMemory) 2 else 1) * HaclBignumQ_LongWords) {
if (it >= HaclBignumQ_LongWords)
0UL
0L
else
tmp[it].convert()
}
Expand Down Expand Up @@ -195,9 +194,9 @@ internal fun ByteArray.toHaclBignumP(
Hacl_Bignum64_new_bn_from_bytes_be(numBytes.convert(), bytes) ?: throw OutOfMemoryError()

// make a copy to Kotlin-managed memory and free the Hacl-managed original
val result = ULongArray((if (doubleMemory) 2 else 1) * numLongWords) {
val result = HaclBignumLongArray((if (doubleMemory) 2 else 1) * numLongWords) {
if (it >= numLongWords)
0UL
0L
else
tmp[it].convert()
}
Expand Down Expand Up @@ -238,7 +237,7 @@ internal fun HaclBignumP.gtP(other: HaclBignumP, mode: ProductionMode): Boolean
}
}

private fun Element.getCompat(other: ProductionGroupContext): ULongArray {
private fun Element.getCompat(other: ProductionGroupContext): HaclBignumLongArray {
context.assertCompatible(other)
return when (this) {
is ProductionElementModP -> this.element
Expand Down Expand Up @@ -292,9 +291,9 @@ class ProductionGroupContext(
twoModP = ProductionElementModP(2U.toHaclBignumP(productionMode), this)
gModP = ProductionElementModP(g, this).acceleratePow() as ProductionElementModP
qModP = ProductionElementModP(
ULongArray(numPLWords.toInt()) {
HaclBignumLongArray(numPLWords.toInt()) {
// Copy from 256-bit to 4096-bit, avoid problems later on. Hopefully.
i -> if (i >= HaclBignumQ_LongWords) 0U else q[i]
i -> if (i >= HaclBignumQ_LongWords) 0 else q[i]
},
this)
zeroModQ = ProductionElementModQ(0U.toHaclBignumP(productionMode), this)
Expand Down Expand Up @@ -535,7 +534,7 @@ class ProductionElementModQ(val element: HaclBignumQ, val groupContext: Producti

override fun inBounds(): Boolean = element ltQ groupContext.q

override fun isZero() = element.contentEquals(groupContext.ZERO_MOD_Q.element)
override fun isZero() = element contentEquals groupContext.ZERO_MOD_Q.element

override fun inBoundsNoZero(): Boolean = inBounds() && !isZero()

Expand All @@ -555,7 +554,7 @@ class ProductionElementModQ(val element: HaclBignumQ, val groupContext: Producti

return when {
thisLtOther -> -1
element.contentEquals(otherElement) -> 0
element contentEquals otherElement -> 0
else -> 1
}
}
Expand Down Expand Up @@ -602,10 +601,10 @@ class ProductionElementModQ(val element: HaclBignumQ, val groupContext: Producti

override operator fun times(other: ElementModQ): ElementModQ {
val result = newZeroBignumQ()
val scratch = ULongArray(HaclBignumQ_LongWords * 2) // 512-bit intermediate value
val scratch = HaclBignumLongArray(HaclBignumQ_LongWords * 2) // 512-bit intermediate value

nativeElems(result, element, other.getCompat(groupContext), scratch) {
r, a, b, s, ->
r, a, b, s ->
Hacl_Bignum256_mul(a, b, s)
Hacl_Bignum256_mod_precomp(groupContext.montCtxQ, s, r)
}
Expand Down Expand Up @@ -662,12 +661,12 @@ class ProductionElementModQ(val element: HaclBignumQ, val groupContext: Producti
// for equality checking; possibly overkill, but if there are ever
// multiple internal representations, this guarantees normalization.
is ProductionElementModQ ->
other.byteArray().contentEquals(this.byteArray()) &&
other.byteArray() contentEquals this.byteArray() &&
other.groupContext.isCompatible(this.groupContext)
else -> false
}

override fun hashCode() = element.hashCode()
override fun hashCode() = element.contentHashCode()

override fun toString() = base64() // unpleasant, but available
}
Expand All @@ -681,7 +680,7 @@ open class ProductionElementModP(val element: HaclBignumP, val groupContext: Pro

override fun inBounds(): Boolean = element.ltP(groupContext.p, groupContext.productionMode)

override fun isZero() = element.contentEquals(groupContext.ZERO_MOD_P.element)
override fun isZero() = element contentEquals groupContext.ZERO_MOD_P.element

override fun inBoundsNoZero() = inBounds() && !isZero()

Expand All @@ -691,7 +690,7 @@ open class ProductionElementModP(val element: HaclBignumP, val groupContext: Pro

return when {
thisLtOther -> -1
element.contentEquals(otherElement) -> 0
element contentEquals otherElement -> 0
else -> 1
}
}
Expand Down Expand Up @@ -727,7 +726,7 @@ open class ProductionElementModP(val element: HaclBignumP, val groupContext: Pro

override operator fun times(other: ElementModP): ElementModP {
val result = newZeroBignumP(groupContext.productionMode)
val scratch = ULongArray(groupContext.numPLWords.toInt() * 2)
val scratch = HaclBignumLongArray(groupContext.numPLWords.toInt() * 2)
nativeElems(result, element, other.getCompat(groupContext), scratch) { r, a, b, s ->
Hacl_Bignum64_mul(groupContext.numPLWords, a, b, s)
Hacl_Bignum64_mod_precomp(groupContext.montCtxP, s, r)
Expand Down Expand Up @@ -764,12 +763,12 @@ open class ProductionElementModP(val element: HaclBignumP, val groupContext: Pro
// for equality checking; possibly overkill, but if there are ever
// multiple internal representations, this guarantees normalization.
is ProductionElementModP ->
other.byteArray().contentEquals(this.byteArray()) &&
other.byteArray() contentEquals this.byteArray() &&
other.groupContext.isCompatible(this.groupContext)
else -> false
}

override fun hashCode() = element.hashCode()
override fun hashCode() = element.contentHashCode()

override fun toString() = base64() // unpleasant, but available

Expand All @@ -791,7 +790,7 @@ class AcceleratedElementModP(p: ProductionElementModP) : ProductionElementModP(p
// for PowModOptions that are never used. Also, the context isn't fully initialized
// when constructing the GroupContext, and this avoids using it until it's ready.

val powRadix by lazy { PowRadix(p, p.groupContext.powRadixOption) }
private val powRadix by lazy { PowRadix(p, p.groupContext.powRadixOption) }

override fun acceleratePow(): ElementModP = this

Expand Down Expand Up @@ -832,4 +831,9 @@ data class ProductionMontgomeryElementModP(

override val context: GroupContext
get() = groupContext

override fun equals(other: Any?): Boolean =
other is ProductionElementModP && element contentEquals other.element

override fun hashCode() = element.contentHashCode()
}
Loading