diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..fdb66337 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,119 @@ +name: CI and Publish + +on: + push: + branches: [ main, releases/* ] + tags: [ v* ] + pull_request: + branches: [ main, releases/* ] + +env: + JAVA_VERSION: '17' + GRADLE_CACHE_PATH: | + ~/.gradle/caches + ~/.gradle/wrapper + USERNAME_GITHUB: ${{ github.actor }} + TOKEN_GITHUB: ${{ secrets.GITHUB_TOKEN }} + +jobs: + build-and-test: + name: Build and Test + runs-on: macos-latest + strategy: + matrix: + module: [ bignum ] + steps: + - uses: actions/checkout@v4 + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: ${{ env.GRADLE_CACHE_PATH }} + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build and Test + run: ./gradlew :${{ matrix.module }}:build --parallel --build-cache --gradle-user-home ~/.gradle + + - name: Archive artifacts + uses: actions/upload-artifact@v2 + with: + name: build-artifacts-${{ matrix.module }} + path: ${{ matrix.module }}/build/libs/ + + publish: + name: Publish Package + runs-on: macos-latest + permissions: + contents: read + packages: write + needs: build-and-test + strategy: + matrix: + module: [ bignum ] + if: startsWith(github.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + + - name: Cache Gradle packages + uses: actions/cache@v3 + with: + path: ${{ env.GRADLE_CACHE_PATH }} + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Setup Java + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: gradle + + - name: Download build artifacts + uses: actions/download-artifact@v2 + with: + name: build-artifacts-${{ matrix.module }} + path: ${{ matrix.module }}/build/libs/ + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Publish package + run: ./gradlew :${{ matrix.module }}:publish + + create-release: + runs-on: ubuntu-latest + permissions: write-all + needs: publish + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Build Changelog + id: changelog + uses: mikepenz/release-changelog-builder-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: ${{ steps.changelog.outputs.changelog }} + + For more details see [CHANGELOG.md](https://github.com/KryptonReborn/kotlin-multiplatform-bignum/blob/main/CHANGELOG.md). + draft: false + prerelease: false \ No newline at end of file diff --git a/bignum/build.gradle.kts b/bignum/build.gradle.kts index 908b2638..1ec5e6fa 100644 --- a/bignum/build.gradle.kts +++ b/bignum/build.gradle.kts @@ -436,7 +436,7 @@ publishing { pom { name.set("Kotlin Multiplatform BigNum") description.set("Kotlin Multiplatform BigNum library") - url.set("https://github.com/ionspin/kotlin-multiplatform-bignum") + url.set("https://github.com/KryptonReborn/kotlin-multiplatform-bignum") licenses { license { name.set("The Apache License, Version 2.0") @@ -451,28 +451,18 @@ publishing { } } scm { - url.set("https://github.com/ionspin/kotlin-multiplatform-bignum") - connection.set("scm:git:git://git@github.com:ionspin/kotlin-multiplatform-bignum.git") - developerConnection.set("scm:git:ssh://git@github.com:ionspin/kotlin-multiplatform-bignum.git") + url.set("https://github.com/KryptonReborn/kotlin-multiplatform-bignum") + connection.set("scm:git:git://git@github.com:KryptonReborn/kotlin-multiplatform-bignum.git") + developerConnection.set("scm:git:ssh://git@github.com:KryptonReborn/kotlin-multiplatform-bignum.git") } } } repositories { maven { - - url = uri(sonatypeStaging) - credentials { - username = sonatypeUsername ?: sonatypeUsernameEnv ?: "" - password = sonatypePassword ?: sonatypePasswordEnv ?: "" - } - } - - maven { - name = "snapshot" - url = uri(sonatypeSnapshots) + uri("https://maven.pkg.github.com/KryptonReborn/kotlin-multiplatform-bignum") credentials { - username = sonatypeUsername ?: sonatypeUsernameEnv ?: "" - password = sonatypePassword ?: sonatypePasswordEnv ?: "" + username = findProperty("gpr.user") as String? ?: System.getenv("USERNAME_GITHUB") + password = findProperty("gpr.token") as String? ?: System.getenv("TOKEN_GITHUB") } } } diff --git a/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/BigInteger.kt b/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/BigInteger.kt index 609e7cdf..f8842c05 100644 --- a/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/BigInteger.kt +++ b/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/BigInteger.kt @@ -413,23 +413,41 @@ class BigInteger internal constructor(wordArray: WordArray, requestedSign: Sign) return u } + // https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm#Modular_integers fun modInverse(modulo: BigInteger): BigInteger { - if (gcd(modulo) != ONE) { - throw ArithmeticException("BigInteger is not invertible. This and modulus are not relatively prime (coprime)") - } - var u = ONE - var w = ZERO - var b = this - var c = modulo - while (c != ZERO) { - val (q, r) = b divrem c - b = c - c = r - val tmpU = u - u = w - w = tmpU - q * w + // Ensure the numbers are coprime + if (this.gcd(modulo) != ONE) { + throw ArithmeticException("BigInteger is not invertible. This and modulus are not relatively prime (coprime).") } - return u + // Initialize variables for the Extended Euclidean Algorithm + var t = ZERO + var newT = ONE + var r = modulo + var newR = this + + // Loop until the remainder is zero + while (newR != ZERO) { + // Compute the quotient + val quotient = r.divide(newR) + + // Update t and newT (coefficient) + val tempT = t + t = newT + newT = tempT - quotient * newT + + // Update r and newR (remainder) + val tempR = r + r = newR + newR = tempR - quotient * newR + } + + // If r is greater than 1, this is not invertible + if (r > ONE) throw ArithmeticException("BigInteger is not invertible.") + + // Ensure the result is positive + if (t < ZERO) t += modulo + + return t } /** @@ -611,8 +629,14 @@ class BigInteger internal constructor(wordArray: WordArray, requestedSign: Sign) return BigInteger(arithmetic.and(this.magnitude, other.magnitude), sign) } + /** Returns a new BigInt with bits combining [this], [other] doing a bitwise `|`/`or` operation. Forces sign to positive. */ override infix fun or(other: BigInteger): BigInteger { - return BigInteger(arithmetic.or(this.magnitude, other.magnitude), sign) + val resultMagnitude = arithmetic.or(this.magnitude, other.magnitude) + val resultSign = when { + isResultZero(resultMagnitude) -> Sign.ZERO + else -> Sign.POSITIVE + } + return BigInteger(resultMagnitude, resultSign) } override infix fun xor(other: BigInteger): BigInteger { diff --git a/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/base63/array/BigInteger63Arithmetic.kt b/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/base63/array/BigInteger63Arithmetic.kt index 397710f0..450c39b9 100644 --- a/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/base63/array/BigInteger63Arithmetic.kt +++ b/bignum/src/commonMain/kotlin/com/ionspin/kotlin/bignum/integer/base63/array/BigInteger63Arithmetic.kt @@ -1972,6 +1972,7 @@ internal object BigInteger63Arithmetic : BigIntegerArithmetic { } override fun or(operand: ULongArray, mask: ULongArray): ULongArray { + if (operand.size < mask.size) return or(mask, operand) return removeLeadingZeros( ULongArray(operand.size) { if (it < mask.size) { diff --git a/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerTest.kt b/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerTest.kt index f40179dd..d7a27784 100644 --- a/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerTest.kt +++ b/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerTest.kt @@ -15,11 +15,10 @@ * */ -package com.ionspin.kotlin.bignum.integer.arithmetic +package com.ionspin.kotlin.bignum.integer -import com.ionspin.kotlin.bignum.integer.BigInteger -import com.ionspin.kotlin.bignum.integer.toBigInteger import kotlin.test.Test +import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -149,15 +148,21 @@ BigIntegerTest { @Test fun testModInverse() { - assertTrue { - var a = BigInteger(11) - var aInverse = a.modInverse(5.toBigInteger()) + val a = BigInteger(11) + val aInverse = a.modInverse(5.toBigInteger()) aInverse == BigInteger(1) } + assertTrue { + val a = BigInteger(54647) + val aInverse = a.modInverse(1157920.toBigInteger()) + aInverse == BigInteger(1141223) + } assertFailsWith { - var a = BigInteger(10) + val a = BigInteger(10) a.modInverse(5.toBigInteger()) + }.also { + assertEquals("BigInteger is not invertible. This and modulus are not relatively prime (coprime).", it.message) } } @@ -166,7 +171,7 @@ BigIntegerTest { val a = 10.toBigInteger() val b = 3.toBigInteger() assertTrue { a.gcd(b) == 1.toBigInteger() } - var c = 6.toBigInteger() + val c = 6.toBigInteger() assertTrue { a.gcd(c) == 2.toBigInteger() } } diff --git a/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/integer/BigIntegerBitwiseOperations.kt b/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/integer/BigIntegerBitwiseOperations.kt index b643af4e..c3603b49 100644 --- a/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/integer/BigIntegerBitwiseOperations.kt +++ b/bignum/src/commonTest/kotlin/com/ionspin/kotlin/bignum/integer/integer/BigIntegerBitwiseOperations.kt @@ -27,6 +27,41 @@ import kotlin.test.assertEquals * on 01-Nov-2019 */ class BigIntegerBitwiseOperations { + @Test + fun andWithZero() { + val operand = BigInteger.parseString("11110000", 2) + val mask = BigInteger.ZERO + + assertEquals(mask, operand and mask) + assertEquals(mask, mask and operand) + } + + @Test + fun andBiggerThanLongMaxWithZero() { + val operand = BigInteger.parseString("9223372036854775808", 10) + val mask = BigInteger.ZERO + + assertEquals(mask, operand and mask) + assertEquals(mask, mask and operand) + } + + @Test + fun orWithZero() { + val operand = BigInteger.parseString("11110000", 2) + val mask = BigInteger.ZERO + + assertEquals(operand, operand or mask) + assertEquals(operand, mask or operand) + } + + @Test + fun orBiggerThanLongMaxWithZero() { + val operand = BigInteger.parseString("9223372036854775808", 10) + val mask = BigInteger.ZERO + + assertEquals(operand, operand or mask) + assertEquals(operand, mask or operand) + } @Test fun xorWithZero() { @@ -35,10 +70,8 @@ class BigIntegerBitwiseOperations { val xorResult = operand xor mask println("Xor result: ${xorResult.toString(2)}") - val expectedResult = operand - - assertEquals(expectedResult, xorResult) - assertEquals(expectedResult, mask xor operand) + assertEquals(operand, xorResult) + assertEquals(operand, mask xor operand) } @Test @@ -46,9 +79,7 @@ class BigIntegerBitwiseOperations { val operand = BigInteger.parseString("9223372036854775808", 10) val mask = BigInteger.ZERO - val expectedResult = operand - - assertEquals(expectedResult, operand xor mask) - assertEquals(expectedResult, mask xor operand) + assertEquals(operand, operand xor mask) + assertEquals(operand, mask xor operand) } } diff --git a/bignum/src/jvmTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerJvmTest.kt b/bignum/src/jvmTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerJvmTest.kt index e89b5331..249aadab 100644 --- a/bignum/src/jvmTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerJvmTest.kt +++ b/bignum/src/jvmTest/kotlin/com/ionspin/kotlin/bignum/integer/BigIntegerJvmTest.kt @@ -41,10 +41,16 @@ class BigIntegerJvmTest { @Test fun testModInverse() { - val a = BigInteger(11) - val aInverse = a.modInverse(5.toBigInteger()) - val aJavaInverse = a.toJavaBigInteger().modInverse(java.math.BigInteger.valueOf(5)) assertTrue { + val a = BigInteger(11) + val aInverse = a.modInverse(5.toBigInteger()) + val aJavaInverse = a.toJavaBigInteger().modInverse(java.math.BigInteger.valueOf(5)) + aInverse.toJavaBigInteger() == aJavaInverse + } + assertTrue { + val a = BigInteger(12312354647) + val aInverse = a.modInverse(121157920.toBigInteger()) + val aJavaInverse = a.toJavaBigInteger().modInverse(java.math.BigInteger.valueOf(121157920)) aInverse.toJavaBigInteger() == aJavaInverse } }