From eb66631ff95e8822048d7364c7b7ee57137ab021 Mon Sep 17 00:00:00 2001 From: Ahmed Moussa Date: Wed, 6 Mar 2024 20:36:39 +0200 Subject: [PATCH] fix: upgrade gradle & libs and remove Apollo dependency (#18) Signed-off-by: Ahmed Moussa --- .github/workflows/pull-request.yml | 7 +- .github/workflows/release-documentation.yml | 9 +- .github/workflows/release.yml | 15 +- build.gradle.kts | 55 +- didpeer/build.gradle.kts | 26 +- .../prism/didcomm/didpeer/base16/Base16.kt | 71 +++ .../didcomm/didpeer/base16/ByteArrayExt.kt | 37 ++ .../prism/didcomm/didpeer/base16/Encoding.kt | 22 + .../prism/didcomm/didpeer/base16/StringExt.kt | 37 ++ .../prism/didcomm/didpeer/base32/Base32.kt | 93 +++ .../didcomm/didpeer/base32/ByteArrayExt.kt | 125 ++++ .../prism/didcomm/didpeer/base32/Encoding.kt | 38 ++ .../prism/didcomm/didpeer/base32/StringExt.kt | 153 +++++ .../prism/didcomm/didpeer/base58/Base58.kt | 71 +++ .../didcomm/didpeer/base58/ByteArrayExt.kt | 41 ++ .../prism/didcomm/didpeer/base58/Encoding.kt | 22 + .../prism/didcomm/didpeer/base58/StringExt.kt | 39 ++ .../prism/didcomm/didpeer/base64/Base64.kt | 533 ++++++++++++++++++ .../didcomm/didpeer/base64/ByteArrayExt.kt | 77 +++ .../prism/didcomm/didpeer/base64/Encoding.kt | 36 ++ .../prism/didcomm/didpeer/base64/StringExt.kt | 91 +++ .../prism/didcomm/didpeer/core/JWKOKP.kt | 4 +- .../prism/didcomm/didpeer/core/Multibase.kt | 6 +- .../prism/didcomm/didpeer/core/Multicodec.kt | 2 +- .../didcomm/didpeer/core/PeerDIDHelper.kt | 4 +- .../didcomm/didpeer/multibase/MultiBase.kt | 133 +++++ .../prism/didcomm/didpeer/varint/VarInt.kt | 29 + .../didcomm/didpeer/varint/VarIntInterface.kt | 8 + .../didcomm/didpeer/base16/Base16Tests.kt | 62 ++ .../didcomm/didpeer/base32/Base32Tests.kt | 127 +++++ .../didcomm/didpeer/base58/Base58Tests.kt | 26 + .../didcomm/didpeer/base64/Base64Tests.kt | 194 +++++++ .../prism/didcomm/didpeer/core/VarIntTests.kt | 49 +- .../didpeer/multibase/MultiBaseTests.kt | 16 + .../didcomm/didpeer/varint/VarIntTests.kt | 16 + gradle/wrapper/gradle-wrapper.properties | 2 +- 36 files changed, 2150 insertions(+), 126 deletions(-) create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/ByteArrayExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Encoding.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/StringExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/ByteArrayExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Encoding.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/StringExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/ByteArrayExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Encoding.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/StringExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/ByteArrayExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Encoding.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/StringExt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBase.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarInt.kt create mode 100644 didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntInterface.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16Tests.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32Tests.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58Tests.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64Tests.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBaseTests.kt create mode 100644 didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntTests.kt diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 012d8f9..9c92902 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -11,8 +11,7 @@ concurrency: cancel-in-progress: true env: - JAVA_VERSION: 11 - NODEJS_VERSION: 16.17.0 + JAVA_VERSION: 17 ATALA_GITHUB_ACTOR: ${{ secrets.ATALA_GITHUB_ACTOR }} ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} @@ -24,7 +23,7 @@ jobs: runs-on: macos-latest steps: - name: Checkout the repo - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.ATALA_GITHUB_TOKEN }} fetch-depth: 0 @@ -33,7 +32,7 @@ jobs: uses: gradle/wrapper-validation-action@v1 - name: Cache gradle - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.gradle/caches diff --git a/.github/workflows/release-documentation.yml b/.github/workflows/release-documentation.yml index 1b6068a..f3bc9b4 100644 --- a/.github/workflows/release-documentation.yml +++ b/.github/workflows/release-documentation.yml @@ -7,8 +7,7 @@ defaults: shell: bash env: - JAVA_VERSION: 11 - NODEJS_VERSION: 16.17.0 + JAVA_VERSION: 17 ATALA_GITHUB_ACTOR: ${{ secrets.ATALA_GITHUB_ACTOR }} ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} @@ -23,13 +22,13 @@ jobs: runs-on: ubuntu-latest steps: - name: "Checkout the repo" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Validate Gradle Wrapper" - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: "Cache gradle" - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.gradle/caches diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f3549cd..491f1c6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ defaults: shell: bash env: - JAVA_VERSION: 11 + JAVA_VERSION: 17 ATALA_GITHUB_ACTOR: ${{ secrets.ATALA_GITHUB_ACTOR }} ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} @@ -22,7 +22,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: token: ${{ secrets.ATALA_GITHUB_TOKEN }} fetch-depth: 0 @@ -32,10 +32,10 @@ jobs: node-version: '>=20.8.1' - name: "Validate Gradle Wrapper" - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v2 - name: "Cache Gradle" - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -50,13 +50,8 @@ jobs: with: java-version: ${{ env.JAVA_VERSION }} distribution: "zulu" - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: "lts/*" - - uses: crazy-max/ghaction-import-gpg@v5 + - uses: crazy-max/ghaction-import-gpg@v6 id: import_gpg with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} diff --git a/build.gradle.kts b/build.gradle.kts index 3ae81fa..2e8c3bb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import java.util.Base64 val publishedMavenId: String = "io.iohk.atala.prism.didcomm" @@ -8,6 +6,8 @@ plugins { id("org.jetbrains.dokka") version "1.9.10" id("maven-publish") id("signing") + kotlin("jvm") version "1.9.22" + id("com.android.library") version "8.1.4" apply false id("io.github.gradle-nexus.publish-plugin") version "2.0.0-rc-1" id("org.jlleitschuh.gradle.ktlint") version "11.6.1" } @@ -20,11 +20,16 @@ buildscript { } dependencies { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") - classpath("com.android.tools.build:gradle:7.2.2") classpath("org.jetbrains.dokka:dokka-base:1.9.10") } } +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(17)) + } +} + group = publishedMavenId allprojects { @@ -100,30 +105,24 @@ allprojects { url.set("https://github.com/input-output-hk/atala-prism-didcomm-kmm") } } - signing { - val base64EncodedAsciiArmoredSigningKey: String = - System.getenv("BASE64_ARMORED_GPG_SIGNING_KEY_MAVEN") ?: "" - val signingKeyPassword: String = - System.getenv("SIGNING_KEY_PASSWORD") ?: "" - useInMemoryPgpKeys( - String( - Base64.getDecoder().decode( - base64EncodedAsciiArmoredSigningKey.toByteArray() + if (System.getenv("BASE64_ARMORED_GPG_SIGNING_KEY_MAVEN") != null) { + if (System.getenv("BASE64_ARMORED_GPG_SIGNING_KEY_MAVEN").isNotBlank()) { + signing { + val base64EncodedAsciiArmoredSigningKey: String = + System.getenv("BASE64_ARMORED_GPG_SIGNING_KEY_MAVEN") ?: "" + val signingKeyPassword: String = + System.getenv("SIGNING_KEY_PASSWORD") ?: "" + useInMemoryPgpKeys( + String( + Base64.getDecoder().decode( + base64EncodedAsciiArmoredSigningKey.toByteArray() + ) + ), + signingKeyPassword ) - ), - signingKeyPassword - ) - sign(this@withType) - } - } - } - repositories { - maven { - this.name = "GitHubPackages" - this.url = uri("https://maven.pkg.github.com/input-output-hk/atala-prism-didcomm-kmm") - credentials { - this.username = System.getenv("ATALA_GITHUB_ACTOR") - this.password = System.getenv("ATALA_GITHUB_TOKEN") + sign(this@withType) + } + } } } } @@ -135,10 +134,6 @@ allprojects { } } -rootProject.plugins.withType(NodeJsRootPlugin::class.java) { - rootProject.extensions.getByType(NodeJsRootExtension::class.java).nodeVersion = "16.17.0" -} - nexusPublishing { repositories { sonatype { diff --git a/didpeer/build.gradle.kts b/didpeer/build.gradle.kts index 0b361b5..19785ad 100644 --- a/didpeer/build.gradle.kts +++ b/didpeer/build.gradle.kts @@ -95,12 +95,9 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("io.iohk.atala.prism.apollo:multibase:1.0.2") - implementation("io.iohk.atala.prism.apollo:varint:1.0.2") - implementation("io.iohk.atala.prism.apollo:base64:1.0.2") - implementation("io.iohk.atala.prism.apollo:base58:1.0.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") - implementation("com.squareup.okio:okio:3.6.0") + implementation("com.squareup.okio:okio:3.7.0") + implementation("com.ionspin.kotlin:bignum:0.3.9") } } val commonTest by getting { @@ -144,11 +141,10 @@ android { sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = 21 - targetSdk = 34 } compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } /** * Because Software Components will not be created automatically for Maven publishing from @@ -175,8 +171,8 @@ tasks.withType().configureEach { } dokkaSourceSets { configureEach { - jdkVersion.set(11) - languageVersion.set("1.8.20") + jdkVersion.set(17) + languageVersion.set("1.9.22") apiVersion.set("2.0") includes.from( "docs/DIDPeer.md" @@ -210,14 +206,4 @@ afterEvaluate { tasks.withType { dependsOn(tasks.withType()) } - if (tasks.findByName("lintAnalyzeDebug") != null) { - tasks.named("lintAnalyzeDebug") { - this.enabled = false - } - } - if (tasks.findByName("lintAnalyzeRelease") != null) { - tasks.named("lintAnalyzeRelease") { - this.enabled = false - } - } } diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16.kt new file mode 100644 index 0000000..d1d347e --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16.kt @@ -0,0 +1,71 @@ +package io.iohk.atala.prism.didcomm.didpeer.base16 + +import com.ionspin.kotlin.bignum.integer.BigInteger +import com.ionspin.kotlin.bignum.integer.Sign + +/** + * Base16 implementation + */ +internal object Base16 { + private val base: BigInteger = BigInteger.parseString("16") + + /** + * Encode string to Base16 + */ + fun encode(input: ByteArray, encoding: Encoding = Encoding.Standard): String { + if (input.isEmpty()) { + return "" + } + var bi = BigInteger.fromByteArray(input, Sign.POSITIVE) + val sb = StringBuilder() + while (bi >= base) { + val mod = bi.mod(base) + sb.insert(0, encoding.alphabet[mod.intValue()]) + bi = bi.subtract(mod).divide(base) + } + sb.insert(0, encoding.alphabet[bi.intValue()]) + // convert leading zeros. + for (b in input) { + if (b.compareTo(0) == 0) { + sb.insert(0, encoding.alphabet[0]) + } else { + break + } + } + return sb.toString() + } + + /** + * Decode string to Base16 + */ + fun decode(input: String, encoding: Encoding = Encoding.Standard): ByteArray { + val bytes = decodeToBigInteger(encoding.alphabet, base, input).toByteArray() + val stripSignByte = bytes.size > 1 && bytes[0].compareTo(0) == 0 && bytes[1] < 0 + var leadingZeros = 0 + var i = 0 + while (input[i] == encoding.alphabet[0]) { + leadingZeros++ + i++ + } + val tmp = ByteArray(bytes.size - (if (stripSignByte) 1 else 0) + leadingZeros) + bytes.copyInto( + tmp, // dest + 0, // dest offset + if (stripSignByte) 1 else 0, + tmp.size - leadingZeros // can be added -1 not sure + ) + return tmp + } + + private fun decodeToBigInteger(alphabet: String, base: BigInteger, input: String): BigInteger { + var bi = BigInteger(0) + for (i in input.length - 1 downTo 0) { + val alphaIndex = alphabet.indexOf(input[i]) + if (alphaIndex == -1) { + throw IllegalStateException("Illegal character " + input[i] + " at " + i) + } + bi = bi.add(BigInteger(alphaIndex.toLong()).multiply(base.pow(input.length - 1 - i))) + } + return bi + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/ByteArrayExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/ByteArrayExt.kt new file mode 100644 index 0000000..8e2efe3 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/ByteArrayExt.kt @@ -0,0 +1,37 @@ +package io.iohk.atala.prism.didcomm.didpeer.base16 + +/** + * Convert [ByteArray] to [CharArray] + * @return [CharArray] + */ +fun ByteArray.asCharArray(): CharArray { + val chars = CharArray(size) + for (i in chars.indices) { + chars[i] = get(i).toInt().toChar() + } + return chars +} + +/** + * Encode a [ByteArray] to Base16 [String] standard + */ +val ByteArray.base16Encoded: String + get() = Base16.encode(this) + +/** + * Decode a [ByteArray] Base16 standard encoded to [String] + */ +val ByteArray.base16Decoded: String + get() = asCharArray().concatToString().base16Encoded + +/** + * Encode a [ByteArray] to Base16 [String] Upper + */ +val ByteArray.base16UpperEncoded: String + get() = Base16.encode(this, Encoding.Upper) + +/** + * Decode a [ByteArray] Base16 Upper encoded to [String] + */ +val ByteArray.base16UpperDecoded: String + get() = asCharArray().concatToString().base16UpperDecoded diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Encoding.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Encoding.kt new file mode 100644 index 0000000..cde813b --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Encoding.kt @@ -0,0 +1,22 @@ +package io.iohk.atala.prism.didcomm.didpeer.base16 + +/** + * Base16 encoding scheme + */ +sealed interface Encoding { + val alphabet: String + + /** + * Base16 Standard + */ + data object Standard : Encoding { + override val alphabet: String = "0123456789abcdef" + } + + /** + * Base16 Upper + */ + data object Upper : Encoding { + override val alphabet: String = "0123456789ABCDEF" + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/StringExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/StringExt.kt new file mode 100644 index 0000000..8e1c73c --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/StringExt.kt @@ -0,0 +1,37 @@ +package io.iohk.atala.prism.didcomm.didpeer.base16 + +/** + * Encode a [String] to Base16 [String] + */ +val String.base16Encoded: String + get() = Base16.encode(this.encodeToByteArray()) + +/** + * Decode a Base16 [String] to [ByteArray]. + */ +val String.base16DecodedBytes: ByteArray + get() = Base16.decode(this) + +/** + * Decode a Base16 [String] to [String]. + */ +val String.base16Decoded: String + get() = this.base16DecodedBytes.decodeToString() + +/** + * Encode a [String] to Base16 [String] Upper + */ +val String.base16UpperEncoded: String + get() = Base16.encode(this.encodeToByteArray(), Encoding.Upper) + +/** + * Decode a Base16 [String] Upper to [ByteArray]. + */ +val String.base16UpperDecodedBytes: ByteArray + get() = Base16.decode(this, Encoding.Upper) + +/** + * Decode a Base16 [String] Upper to [String]. + */ +val String.base16UpperDecoded: String + get() = this.base16UpperDecodedBytes.decodeToString() diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32.kt new file mode 100644 index 0000000..9a0bf38 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32.kt @@ -0,0 +1,93 @@ +package io.iohk.atala.prism.didcomm.didpeer.base32 + +import com.ionspin.kotlin.bignum.integer.BigInteger + +/** + * Base32 implementation + */ +internal final object Base32 { + private val base: BigInteger = BigInteger.parseString("32") + + /** + * Encode string to Base32 + */ + fun encode(input: ByteArray, encoding: Encoding = Encoding.Standard, paddingEnabled: Boolean = true): String { + if (input.contentEquals("".encodeToByteArray())) { + return "" + } + val output = StringBuilder() + var buffer = 0 + var bits = 0 + + for (byte in input) { + buffer = (buffer shl 8) or (byte.toInt() and 0xFF) + bits += 8 + + while (bits >= 5) { + val index = (buffer shr (bits - 5)) and 0x1F + output.append(encoding.alphabet[index]) + bits -= 5 + } + } + + if (bits > 0) { + buffer = (buffer shl (5 - bits)) + val index = (buffer and 0x1F) + output.append(encoding.alphabet[index]) + } + + val padding = (8 - output.length % 8) % 8 + repeat(padding) { + output.append('=') + } + + return output.toString() + } + + /** + * Decode string to Base32 + */ + fun decode(input: String, encoding: Encoding = Encoding.Standard): ByteArray { + val bytes = decodeToBigInteger(encoding.alphabet, base, input).toByteArray().dropLastWhile { + it.toInt() == 0 + }.toByteArray() + + // Determine if the first byte is a sign byte (0x00) and needs to be stripped + val stripSignByte = bytes.size > 1 && bytes[0].compareTo(0) == 0 && bytes[1] < 0 + + // Count the number of leading zeros in the input string + var leadingZeros = 0 + var i = 0 + while (input[i] == encoding.alphabet[0]) { + leadingZeros++ + i++ + } + + // Allocate a temporary byte array with the correct size, accounting for the sign byte and leading zeros + val tmp = ByteArray(bytes.size - (if (stripSignByte) 1 else 0) + leadingZeros) + + // Copy the decoded bytes into the temporary array, skipping the sign byte if necessary + bytes.copyInto( + tmp, // destination array + 0, // destination offset + if (stripSignByte) 1 else 0, // source offset + tmp.size - leadingZeros // number of bytes to copy + ) + + return tmp + } + + private fun decodeToBigInteger(alphabet: String, base: BigInteger, input: String): BigInteger { + var bi = BigInteger(0) + for (i in input.length - 1 downTo 0) { + val alphaIndex = alphabet.indexOf(input[i]) + if (alphaIndex == -1 && input[i] != '=') { + throw IllegalStateException("Illegal character " + input[i] + " at " + i) + } + if (input[i] != '=') { + bi = bi.add(BigInteger(alphaIndex.toLong()).multiply(base.pow(input.length - 1 - i))) + } + } + return bi + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/ByteArrayExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/ByteArrayExt.kt new file mode 100644 index 0000000..d81071b --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/ByteArrayExt.kt @@ -0,0 +1,125 @@ +package io.iohk.atala.prism.didcomm.didpeer.base32 + +/** + * Convert [ByteArray] to [CharArray] + * @return [CharArray] + */ +fun ByteArray.asCharArray(): CharArray { + val chars = CharArray(size) + for (i in chars.indices) { + chars[i] = get(i).toInt().toChar() + } + return chars +} + +// Standard + +/** + * Encode a [ByteArray] to Base32 [String] standard + */ +val ByteArray.base32Encoded: String + get() = Base32.encode(this, paddingEnabled = false) + +/** + * Decode a [ByteArray] Base32 standard encoded to [String] + */ +val ByteArray.base32Decoded: String + get() = asCharArray().concatToString().base32Encoded + +// Standard with padding + +/** + * Encode a [ByteArray] to Base32 [String] standard with padding + */ +val ByteArray.base32PadEncoded: String + get() = Base32.encode(this, Encoding.Standard) + +/** + * Decode a [ByteArray] Base32 standard with padding encoded to [String] + */ +val ByteArray.base32PadDecoded: String + get() = asCharArray().concatToString().base32PadEncoded + +// Upper + +/** + * Encode a [ByteArray] to Base32 [String] upper + */ +val ByteArray.base32UpperEncoded: String + get() = Base32.encode(this, Encoding.Upper, paddingEnabled = false) + +/** + * Decode a [ByteArray] Base32 Upper encoded to [String] + */ +val ByteArray.base32UpperDecoded: String + get() = asCharArray().concatToString().base32UpperEncoded + +// Upper with padding + +/** + * Encode a [ByteArray] to Base32 [String] Upper with padding + */ +val ByteArray.base32UpperPadEncoded: String + get() = Base32.encode(this, Encoding.Upper) + +/** + * Decode a [ByteArray] Base32 Upper with padding encoded to [String] + */ +val ByteArray.base32UpperPadDecoded: String + get() = asCharArray().concatToString().base32UpperPadEncoded + +// Hex + +/** + * Encode a [ByteArray] to Base32 [String] hex + */ +val ByteArray.base32HexEncoded: String + get() = Base32.encode(this, Encoding.Hex, paddingEnabled = false) + +/** + * Decode a [ByteArray] Base32 Hex encoded to [String] + */ +val ByteArray.base32HexDecoded: String + get() = asCharArray().concatToString().base32HexEncoded + +// Hex with padding + +/** + * Encode a [ByteArray] to Base32 [String] Hex with padding + */ +val ByteArray.base32HexPadEncoded: String + get() = Base32.encode(this, Encoding.Hex) + +/** + * Decode a [ByteArray] Base32 Hex with padding encoded to [String] + */ +val ByteArray.base32HexPadDecoded: String + get() = asCharArray().concatToString().base32HexPadEncoded + +// Hex Upper + +/** + * Encode a [ByteArray] to Base32 [String] Hex Upper + */ +val ByteArray.base32HexUpperEncoded: String + get() = Base32.encode(this, Encoding.HexUpper) + +/** + * Decode a [ByteArray] Base32 Hex Upper encoded to [String] + */ +val ByteArray.base32HexUpperDecoded: String + get() = asCharArray().concatToString().base32HexUpperEncoded + +// Hex Upper with padding + +/** + * Encode a [ByteArray] to Base32 [String] Hex Upper with padding + */ +val ByteArray.base32HexUpperPadEncoded: String + get() = Base32.encode(this, Encoding.HexUpper) + +/** + * Decode a [ByteArray] Base32 HexUpper with padding encoded to [String] + */ +val ByteArray.base32HexUpperPadDecoded: String + get() = asCharArray().concatToString().base32HexUpperPadEncoded diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Encoding.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Encoding.kt new file mode 100644 index 0000000..153cd46 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Encoding.kt @@ -0,0 +1,38 @@ +package io.iohk.atala.prism.didcomm.didpeer.base32 + +/** + * Base32 encoding scheme + * + * TODO: Figure out a way to put both type with padded version in on scheme + */ +sealed interface Encoding { + val alphabet: String + + /** + * Base32 Standard + */ + data object Standard : Encoding { + override val alphabet: String = "abcdefghijklmnopqrstuvwxyz234567" + } + + /** + * Base32 Upper + */ + data object Upper : Encoding { + override val alphabet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + } + + /** + * Base32 Hex + */ + data object Hex : Encoding { + override val alphabet: String = "0123456789abcdefghijklmnopqrstuvw" + } + + /** + * Base32 Hex Upper + */ + data object HexUpper : Encoding { + override val alphabet: String = "0123456789ABCDEFGHIJKLMNOPQRSTUVW" + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/StringExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/StringExt.kt new file mode 100644 index 0000000..bbf6050 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/StringExt.kt @@ -0,0 +1,153 @@ +package io.iohk.atala.prism.didcomm.didpeer.base32 + +// Standard +/** + * Encode a [String] to Base32 [String] + */ +val String.base32Encoded: String + get() = Base32.encode(this.encodeToByteArray(), paddingEnabled = false) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32DecodedBytes: ByteArray + get() = Base32.decode(this) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32Decoded: String + get() = this.base32DecodedBytes.decodeToString() + +// Standard With Padding +/** + * Encode a [String] to Base32 [String] + */ +val String.base32PadEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.Standard) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32PadDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.Standard) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32PadDecoded: String + get() = this.base32PadDecodedBytes.decodeToString() + +// Upper +/** + * Encode a [String] to Base32 [String] + */ +val String.base32UpperEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.Upper, paddingEnabled = false) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32UpperDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.Upper) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32UpperDecoded: String + get() = this.base32UpperDecodedBytes.decodeToString() + +// Upper With Padding +/** + * Encode a [String] to Base32 [String] + */ +val String.base32UpperPadEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.Upper) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32UpperPadDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.Upper) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32UpperPadDecoded: String + get() = this.base32UpperPadDecodedBytes.decodeToString() + +// Hex +/** + * Encode a [String] to Base32 [String] + */ +val String.base32HexEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.Hex, paddingEnabled = false) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32HexDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.Hex) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32HexDecoded: String + get() = this.base32HexDecodedBytes.decodeToString() + +// Hex with padding +/** + * Encode a [String] to Base32 [String] + */ +val String.base32HexPadEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.Hex) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32HexPadDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.Hex) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32HexPadDecoded: String + get() = this.base32HexPadDecodedBytes.decodeToString() + +// Hex Upper +/** + * Encode a [String] to Base32 [String] + */ +val String.base32HexUpperEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.HexUpper, paddingEnabled = false) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32HexUpperDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.HexUpper) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32HexUpperDecoded: String + get() = this.base32HexUpperDecodedBytes.decodeToString() + +// Hex Upper with padding +/** + * Encode a [String] to Base32 [String] + */ +val String.base32HexUpperPadEncoded: String + get() = Base32.encode(this.encodeToByteArray(), Encoding.HexUpper) + +/** + * Decode a Base32 [String] to [ByteArray]. + */ +val String.base32HexUpperPadDecodedBytes: ByteArray + get() = Base32.decode(this, Encoding.HexUpper) + +/** + * Decode a Base32 [String] to [String]. + */ +val String.base32HexUpperPadDecoded: String + get() = this.base32HexUpperPadDecodedBytes.decodeToString() diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58.kt new file mode 100644 index 0000000..31ff38e --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58.kt @@ -0,0 +1,71 @@ +package io.iohk.atala.prism.didcomm.didpeer.base58 + +import com.ionspin.kotlin.bignum.integer.BigInteger +import com.ionspin.kotlin.bignum.integer.Sign + +/** + * Base58 implementation + */ +internal object Base58 { + private val base: BigInteger = BigInteger.parseString("58") + + /** + * Encode string to Base58 + */ + fun encode(input: ByteArray, encoding: Encoding = Encoding.BTC): String { + if (input.contentEquals("".encodeToByteArray())) { + return "" + } + var bi = BigInteger.fromByteArray(input, Sign.POSITIVE) + val sb = StringBuilder() + while (bi >= base) { + val mod = bi.mod(base) + sb.insert(0, encoding.alphabet[mod.intValue()]) + bi = bi.subtract(mod).divide(base) + } + sb.insert(0, encoding.alphabet[bi.intValue()]) + // convert leading zeros. + for (b in input) { + if (b.compareTo(0) == 0) { + sb.insert(0, encoding.alphabet[0]) + } else { + break + } + } + return sb.toString() + } + + /** + * Decode string to Base58 + */ + fun decode(input: String, encoding: Encoding = Encoding.BTC): ByteArray { + val bytes = decodeToBigInteger(encoding.alphabet, base, input).toByteArray() + val stripSignByte = bytes.size > 1 && bytes[0].compareTo(0) == 0 && bytes[1] < 0 + var leadingZeros = 0 + var i = 0 + while (input[i] == encoding.alphabet[0]) { + leadingZeros++ + i++ + } + val tmp = ByteArray(bytes.size - (if (stripSignByte) 1 else 0) + leadingZeros) + bytes.copyInto( + tmp, // dest + 0, // dest offset + if (stripSignByte) 1 else 0, + tmp.size - leadingZeros // can be added -1 not sure + ) + return tmp + } + + private fun decodeToBigInteger(alphabet: String, base: BigInteger, input: String): BigInteger { + var bi = BigInteger(0) + for (i in input.length - 1 downTo 0) { + val alphaIndex = alphabet.indexOf(input[i]) + if (alphaIndex == -1) { + throw IllegalStateException("Illegal character " + input[i] + " at " + i) + } + bi = bi.add(BigInteger(alphaIndex.toLong()).multiply(base.pow(input.length - 1 - i))) + } + return bi + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/ByteArrayExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/ByteArrayExt.kt new file mode 100644 index 0000000..35abf98 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/ByteArrayExt.kt @@ -0,0 +1,41 @@ +package io.iohk.atala.prism.didcomm.didpeer.base58 + +/** + * Convert [ByteArray] to [CharArray] + * @return [CharArray] + */ +fun ByteArray.asCharArray(): CharArray { + val chars = CharArray(size) + for (i in chars.indices) { + chars[i] = get(i).toInt().toChar() + } + return chars +} + +// BTC + +/** + * Encode a [ByteArray] to Base58 [String] standard + */ +val ByteArray.base58BtcEncoded: String + get() = Base58.encode(this) + +/** + * Decode a [ByteArray] Base58 BTC encoded to [String] + */ +val ByteArray.base58BtcDecoded: String + get() = asCharArray().concatToString().base58BtcEncoded + +// Flickr + +/** + * Encode a [ByteArray] to Base58 [String] standard + */ +val ByteArray.base58FlickrEncoded: String + get() = Base58.encode(this, Encoding.Flickr) + +/** + * Decode a [ByteArray] Base58 BTC encoded to [String] + */ +val ByteArray.base58FlickrDecoded: String + get() = asCharArray().concatToString().base58FlickrEncoded diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Encoding.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Encoding.kt new file mode 100644 index 0000000..e772529 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Encoding.kt @@ -0,0 +1,22 @@ +package io.iohk.atala.prism.didcomm.didpeer.base58 + +/** + * Base58 encoding scheme + */ +sealed interface Encoding { + val alphabet: String + + /** + * Base58 BTC => Standard + */ + data object BTC : Encoding { + override val alphabet: String = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + } + + /** + * Base58 Flickr + */ + data object Flickr : Encoding { + override val alphabet: String = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ" + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/StringExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/StringExt.kt new file mode 100644 index 0000000..c57ff17 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/StringExt.kt @@ -0,0 +1,39 @@ +package io.iohk.atala.prism.didcomm.didpeer.base58 + +// BTC +/** + * Encode a [String] to Base58 [String] + */ +val String.base58BtcEncoded: String + get() = Base58.encode(this.encodeToByteArray()) + +/** + * Decode a Base58 [String] to [ByteArray]. + */ +val String.base58BtcDecodedBytes: ByteArray + get() = Base58.decode(this) + +/** + * Decode a Base58 [String] to [String]. + */ +val String.base58BtcDecoded: String + get() = this.base58BtcDecodedBytes.decodeToString() + +// Flickr +/** + * Encode a [String] to Base58 [String] + */ +val String.base58FlickrEncoded: String + get() = Base58.encode(this.encodeToByteArray(), Encoding.Flickr) + +/** + * Decode a Base58 [String] to [ByteArray]. + */ +val String.base58FlickrDecodedBytes: ByteArray + get() = Base58.decode(this, Encoding.Flickr) + +/** + * Decode a Base58 [String] to [String]. + */ +val String.base58FlickrDecoded: String + get() = this.base58FlickrDecodedBytes.decodeToString() diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64.kt new file mode 100644 index 0000000..99d7f65 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64.kt @@ -0,0 +1,533 @@ +package io.iohk.atala.prism.didcomm.didpeer.base64 + +import kotlin.math.min + +internal object Base64 { + + /** + * Encode ByteArray to Base64 String + */ + fun encodeToString(input: ByteArray, encoding: Encoding = Encoding.Standard): String { + return when (encoding) { + Encoding.Standard -> getEncoder().withoutPadding().encodeToString(input) + Encoding.StandardPad -> getEncoder().encodeToString(input) + Encoding.UrlSafe -> getUrlEncoder().withoutPadding().encodeToString(input) + Encoding.UrlSafePad -> getUrlEncoder().encodeToString(input) + } + } + + /** + * Encode ByteArray to Base64 ByteArray + */ + fun encode(input: ByteArray, encoding: Encoding = Encoding.Standard): ByteArray { + return when (encoding) { + Encoding.Standard -> getEncoder().withoutPadding().encode(input) + Encoding.StandardPad -> getEncoder().encode(input) + Encoding.UrlSafe -> getUrlEncoder().withoutPadding().encode(input) + Encoding.UrlSafePad -> getUrlEncoder().encode(input) + } + } + + /** + * Decode string to Base64 + */ + fun decode(input: String, encoding: Encoding = Encoding.Standard): ByteArray { + return when (encoding) { + Encoding.Standard, Encoding.StandardPad -> getDecoder().decode(input) + Encoding.UrlSafe, Encoding.UrlSafePad -> getUrlDecoder().decode(input) + } + } + + private fun getEncoder(): Encoder { + return Encoder.RFC4648 + } + + private fun getDecoder(): Decoder { + return Decoder.RFC4648 + } + + private fun getUrlEncoder(): Encoder { + return Encoder.RFC4648_URLSAFE + } + + private fun getUrlDecoder(): Decoder { + return Decoder.RFC4648_URLSAFE + } + + /** + * This class implements a decoder for decoding byte data using the + * Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + * + * The Base64 padding character `'='` is accepted and + * interpreted as the end of the encoded byte data, but is not + * required. So if the final unit of the encoded byte data only has + * two or three Base64 characters (without the corresponding padding + * character(s) padded), they are decoded as if followed by padding + * character(s). If there is a padding character present in the + * final unit, the correct number of padding character(s) must be + * present, otherwise `IllegalArgumentException` ( + * `IOException` when reading from a Base64 stream) is thrown + * during decoding. + * + * + * Instances of [Decoder] class are safe for use by + * multiple concurrent threads. + * + * + * Unless otherwise noted, passing a `null` argument to + * a method of this class will cause a + * [NullPointerException][NullPointerException] to + * be thrown. + * + * @see Encoder + * + * @since 1.8 + */ + private class Decoder private constructor(private val isURL: Boolean, private val isMIME: Boolean) { + /** + * Decodes all bytes from the input byte array using the [Base64] + * encoding scheme, writing the results into a newly-allocated output + * byte array. The returned byte array is of the length of the resulting + * bytes. + * + * @param src + * the byte array to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if `src` is not in valid Base64 scheme + */ + fun decode(src: ByteArray): ByteArray { + val dst = ByteArray(outLength(src, 0, src.size)) + val ret = decode0(src, 0, src.size, dst) + if (ret != dst.size) { + dst.copyInto(ByteArray(ret)) + // dst = java.util.Arrays.copyOf(dst, ret) + } + return dst + } + + /** + * Decodes a Base64 encoded String into a newly-allocated byte array + * using the [Base64] encoding scheme. + * + * + * An invocation of this method has exactly the same effect as invoking + * `decode(src.getBytes(StandardCharsets.ISO_8859_1))` + * + * @param src + * the string to decode + * + * @return A newly-allocated byte array containing the decoded bytes. + * + * @throws IllegalArgumentException + * if `src` is not in valid Base64 scheme + */ + fun decode(src: String): ByteArray { + return decode(src.encodeToByteArray()) + // return decode(src.toByteArray(java.nio.charset.StandardCharsets.ISO_8859_1)) + } + + /** + * Decodes all bytes from the input byte array using the [Base64] + * encoding scheme, writing the results into the given output byte array, + * starting at offset 0. + * + * + * It is the responsibility of the invoker of this method to make + * sure the output byte array `dst` has enough space for decoding + * all bytes from the input byte array. No bytes will be be written to + * the output byte array if the output byte array is not big enough. + * + * + * If the input byte array is not in valid Base64 encoding scheme + * then some bytes may have been written to the output byte array before + * [IllegalArgumentException] is thrown. + * + * @param src + * the byte array to decode + * @param dst + * the output byte array + * + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException + * if `src` is not in valid Base64 scheme, or `dst` + * does not have enough space for decoding all input bytes. + */ + @Throws(IllegalArgumentException::class) + fun decode(src: ByteArray, dst: ByteArray): Int { + val len = outLength(src, 0, src.size) + if (dst.size < len) { + throw IllegalArgumentException("Output byte array is too small for decoding all input bytes") + } + return decode0(src, 0, src.size, dst) + } + + @Suppress("NAME_SHADOWING") + @Throws(IllegalArgumentException::class) + private fun outLength(src: ByteArray, sp: Int, sl: Int): Int { + var sp = sp + val base64 = if (isURL) { + fromBase64URL + } else { + fromBase64 + } + var paddings = 0 + var len = sl - sp + if (len == 0) { + return 0 + } + if (len < 2) { + if (isMIME && base64[0] == -1) { + return 0 + } + throw IllegalArgumentException("Input byte[] should at least have 2 bytes for base64 bytes") + } + if (isMIME) { + // scan all bytes to fill out all non-alphabet. a performance + // trade-off of pre-scan or Arrays.copyOf + var n = 0 + while (sp < sl) { + var b = src[sp++].toInt() and 0xff + if (b == '='.code) { + len -= sl - sp + 1 + break + } + if (base64[b].also { b = it } == -1) { + n++ + } + } + len -= n + } else { + if (src[sl - 1] == '='.code.toByte()) { + paddings++ + if (src[sl - 2] == '='.code.toByte()) { + paddings++ + } + } + } + if (paddings == 0 && len and 0x3 != 0) paddings = 4 - (len and 0x3) + return 3 * ((len + 3) / 4) - paddings + } + + @Suppress("NAME_SHADOWING") + @Throws(IllegalArgumentException::class) + private fun decode0(src: ByteArray, sp: Int, sl: Int, dst: ByteArray): Int { + var sp = sp + val base64 = if (isURL) fromBase64URL else fromBase64 + var dp = 0 + var bits = 0 + var shiftto = 18 // pos of first byte of 4-byte atom + while (sp < sl) { + var b = src[sp++].toInt() and 0xff + if (base64[b].also { b = it } < 0) { + if (b == -2) { // padding byte '=' + // = shiftto==18 unnecessary padding + // x= shiftto==12 a dangling single x + // x to be handled together with non-padding case + // xx= shiftto==6&&sp==sl missing last = + // xx=y shiftto==6 last is not = + if (shiftto == 6 && (sp == sl || src[sp++] != '='.code.toByte()) || + shiftto == 18 + ) { + throw IllegalArgumentException("Input byte array has wrong 4-byte ending unit") + } + break + } + if (isMIME) { // skip if for rfc2045 + continue + } else { + throw IllegalArgumentException("Illegal base64 character ${src[sp - 1].toInt().toString(16)}") + } + } + bits = bits or (b shl shiftto) + shiftto -= 6 + if (shiftto < 0) { + dst[dp++] = (bits shr 16).toByte() + dst[dp++] = (bits shr 8).toByte() + dst[dp++] = bits.toByte() + shiftto = 18 + bits = 0 + } + } + // reached end of byte array or hit padding '=' characters. + when (shiftto) { + 6 -> { + dst[dp++] = (bits shr 16).toByte() + } + 0 -> { + dst[dp++] = (bits shr 16).toByte() + dst[dp++] = (bits shr 8).toByte() + } + 12 -> { + // dangling single "x", incorrectly encoded. + throw IllegalArgumentException("Last unit does not have enough valid bits") + } + } + // anything left is invalid, if is not MIME. + // if MIME, ignore all non-base64 character + while (sp < sl) { + if (isMIME && base64[src[sp++].toInt()] < 0) { + continue + } + throw IllegalArgumentException("Input byte array has incorrect ending byte at $sp") + } + return dp + } + + companion object { + /** + * Lookup table for decoding unicode characters drawn from the + * "Base64 Alphabet" (as specified in Table 1 of RFC 2045) into + * their 6-bit positive integer equivalents. Characters that + * are not in the Base64 alphabet but fall within the bounds of + * the array are encoded to -1. + * + */ + private val fromBase64 = IntArray(256) + + init { + fromBase64.fill(-1) + // java.util.Arrays.fill(fromBase64, -1) + for (i in Encoder.toBase64.indices) fromBase64[Encoder.toBase64.get(i).code] = + i + fromBase64['='.code] = -2 + } + + /** + * Lookup table for decoding "URL and Filename safe Base64 Alphabet" + * as specified in Table2 of the RFC 4648. + */ + private val fromBase64URL = IntArray(256) + + init { + fromBase64URL.fill(-1) + // java.util.Arrays.fill(fromBase64URL, -1) + for (i in Encoder.toBase64URL.indices) fromBase64URL[Encoder.toBase64URL[i].code] = i + fromBase64URL['='.code] = -2 + } + + val RFC4648 = Decoder(false, false) + val RFC4648_URLSAFE = Decoder(true, false) + val RFC2045 = Decoder(false, true) + } + } + + /** + * This class implements an encoder for encoding byte data using + * the Base64 encoding scheme as specified in RFC 4648 and RFC 2045. + * + * + * Instances of [Encoder] class are safe for use by + * multiple concurrent threads. + * + * + * Unless otherwise noted, passing a `null` argument to + * a method of this class will cause a + * [NullPointerException][java.lang.NullPointerException] to + * be thrown. + * + * @see Decoder + * + * @since 1.8 + */ + private class Encoder private constructor( + private val isURL: Boolean, + private val newline: ByteArray?, + private val linemax: Int, + private val doPadding: Boolean + ) { + private fun outLength(srclen: Int): Int { + var len = if (doPadding) { + 4 * ((srclen + 2) / 3) + } else { + val n = srclen % 3 + 4 * (srclen / 3) + if (n == 0) 0 else n + 1 + } + if (linemax > 0) { // line separators + len += (len - 1) / linemax * newline!!.size + } + return len + } + + /** + * Encodes all bytes from the specified byte array into a newly-allocated + * byte array using the [Base64] encoding scheme. The returned byte + * array is of the length of the resulting bytes. + * + * @param src + * the byte array to encode + * @return A newly-allocated byte array containing the resulting + * encoded bytes. + */ + fun encode(src: ByteArray): ByteArray { + val len = outLength(src.size) // dst array size + val dst = ByteArray(len) + val ret = encode0(src, 0, src.size, dst) + return if (ret != dst.size) { + dst.copyInto(ByteArray(ret)) + // java.util.Arrays.copyOf(dst, ret) + } else { + dst + } + } + + /** + * Encodes all bytes from the specified byte array using the + * [Base64] encoding scheme, writing the resulting bytes to the + * given output byte array, starting at offset 0. + * + * + * It is the responsibility of the invoker of this method to make + * sure the output byte array `dst` has enough space for encoding + * all bytes from the input byte array. No bytes will be written to the + * output byte array if the output byte array is not big enough. + * + * @param src + * the byte array to encode + * @param dst + * the output byte array + * @return The number of bytes written to the output byte array + * + * @throws IllegalArgumentException if `dst` does not have enough + * space for encoding all input bytes. + */ + @Throws(IllegalArgumentException::class) + fun encode(src: ByteArray, dst: ByteArray): Int { + val len = outLength(src.size) // dst array size + if (dst.size < len) { + throw IllegalArgumentException("Output byte array is too small for encoding all input bytes") + } + return encode0(src, 0, src.size, dst) + } + + /** + * Encodes the specified byte array into a String using the [Base64] + * encoding scheme. + * + * + * This method first encodes all input bytes into a base64 encoded + * byte array and then constructs a new String by using the encoded byte + * array and the [ ISO-8859-1][java.nio.charset.StandardCharsets.ISO_8859_1] charset. + * + * + * In other words, an invocation of this method has exactly the same + * effect as invoking + * `new String(encode(src), StandardCharsets.ISO_8859_1)`. + * + * @param src + * the byte array to encode + * @return A String containing the resulting Base64 encoded characters + */ + fun encodeToString(src: ByteArray): String { + val encoded: ByteArray = encode(src) + return encoded.decodeToString() + // return String(encoded, 0, 0, encoded.size) + } + + /** + * Returns an encoder instance that encodes equivalently to this one, + * but without adding any padding character at the end of the encoded + * byte data. + * + * + * The encoding scheme of this encoder instance is unaffected by + * this invocation. The returned encoder instance should be used for + * non-padding encoding operation. + * + * @return an equivalent encoder that encodes without adding any + * padding character at the end + */ + fun withoutPadding(): Encoder { + return if (!doPadding) this else Encoder(isURL, newline, linemax, false) + } + + @Suppress("UNUSED_CHANGED_VALUE") + private fun encode0(src: ByteArray, off: Int, end: Int, dst: ByteArray): Int { + val base64 = if (isURL) toBase64URL else toBase64 + var sp = off + var slen = (end - off) / 3 * 3 + val sl = off + slen + if (linemax > 0 && slen > linemax / 4 * 3) slen = linemax / 4 * 3 + var dp = 0 + while (sp < sl) { + val sl0: Int = min(sp + slen, sl) + var sp0 = sp + var dp0 = dp + while (sp0 < sl0) { + val bits = src[sp0++].toInt() and 0xff shl 16 or ( + src[sp0++].toInt() and 0xff shl 8 + ) or + (src[sp0++].toInt() and 0xff) + dst[dp0++] = base64[bits ushr 18 and 0x3f].code.toByte() + dst[dp0++] = base64[bits ushr 12 and 0x3f].code.toByte() + dst[dp0++] = base64[bits ushr 6 and 0x3f].code.toByte() + dst[dp0++] = base64[bits and 0x3f].code.toByte() + } + val dlen = (sl0 - sp) / 3 * 4 + dp += dlen + sp = sl0 + if (dlen == linemax && sp < end) { + for (b in newline!!) { + dst[dp++] = b + } + } + } + if (sp < end) { // 1 or 2 leftover bytes + val b0 = src[sp++].toInt() and 0xff + dst[dp++] = base64[b0 shr 2].code.toByte() + if (sp == end) { + dst[dp++] = base64[b0 shl 4 and 0x3f].code.toByte() + if (doPadding) { + dst[dp++] = '='.code.toByte() + dst[dp++] = '='.code.toByte() + } + } else { + val b1 = src[sp++].toInt() and 0xff + dst[dp++] = base64[b0 shl 4 and 0x3f or (b1 shr 4)].code.toByte() + dst[dp++] = base64[b1 shl 2 and 0x3f].code.toByte() + if (doPadding) { + dst[dp++] = '='.code.toByte() + } + } + } + return dp + } + + companion object { + /** + * This array is a lookup table that translates 6-bit positive integer + * index values into their "Base64 Alphabet" equivalents as specified + * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648). + */ + val toBase64 = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' + ) + + /** + * It's the lookup table for "URL and Filename safe Base64" as specified + * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and + * '_'. This table is used when BASE64_URL is specified. + */ + val toBase64URL = charArrayOf( + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' + ) + private const val MIMELINEMAX = 76 + private val CRLF = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte()) + val RFC4648 = Encoder(false, null, -1, true) + val RFC4648_URLSAFE = Encoder(true, null, -1, true) + val RFC2045 = Encoder(false, CRLF, MIMELINEMAX, true) + } + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/ByteArrayExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/ByteArrayExt.kt new file mode 100644 index 0000000..cc0a1e8 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/ByteArrayExt.kt @@ -0,0 +1,77 @@ +package io.iohk.atala.prism.didcomm.didpeer.base64 + +/** + * Convert [ByteArray] to [CharArray] + * @return [CharArray] + */ +fun ByteArray.asCharArray(): CharArray { + val chars = CharArray(size) + for (i in chars.indices) { + chars[i] = get(i).toInt().toChar() + } + return chars +} + +// Base64Standard + +/** + * Encode a [ByteArray] to Base64 [String] standard encoding + * RFC 4648 Section 4 + */ +val ByteArray.base64Encoded: String + get() = Base64.encodeToString(this) + +/** + * Decode a [ByteArray] Base64 standard encoded to [String] + * RFC 4648 Section 4 + */ +val ByteArray.base64Decoded: String + get() = Base64.decode(this.decodeToString()).decodeToString() + +// Base64Standard with padding + +/** + * Encode a [ByteArray] to Base64 [String] standard encoding + * RFC 4648 Section 4 + */ +val ByteArray.base64PadEncoded: String + get() = Base64.encodeToString(this, Encoding.StandardPad) + +/** + * Decode a [ByteArray] Base64 standard encoded to [String] + * RFC 4648 Section 4 + */ +val ByteArray.base64PadDecoded: String + get() = Base64.decode(this.decodeToString(), Encoding.StandardPad).decodeToString() + +// Base64URL + +/** + * Decode a [ByteArray] Base64 URL-safe encoded to [String]. + * RFC 4648 Section 5 + */ +val ByteArray.base64UrlDecoded: String + get() = Base64.decode(this.decodeToString(), Encoding.UrlSafe).decodeToString() + +/** + * Encode a [ByteArray] to Base64 URL-safe encoded [String]. + * RFC 4648 Section 5 + */ +val ByteArray.base64UrlEncoded: String + get() = Base64.encodeToString(this, Encoding.UrlSafe) + +// Base64URL with padding + +/** + * Decode a [ByteArray] Base64 URL-safe encoded to [String]. + * RFC 4648 Section 5 + */ +val ByteArray.base64UrlPadDecoded: String + get() = Base64.encodeToString(this, Encoding.UrlSafePad) + +/** + * Encode a [ByteArray] to Base64 URL-safe encoded [String]. + * RFC 4648 Section 5 + */ +val ByteArray.base64UrlPadEncoded: String + get() = Base64.encodeToString(this, Encoding.UrlSafePad) diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Encoding.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Encoding.kt new file mode 100644 index 0000000..355a38f --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Encoding.kt @@ -0,0 +1,36 @@ +package io.iohk.atala.prism.didcomm.didpeer.base64 + +/** + * Base64 encoding scheme + */ +sealed interface Encoding { + val alphabet: String + + /** + * Base64 Standard + */ + data object Standard : Encoding { + override val alphabet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + } + + /** + * Base64 Standard + */ + data object StandardPad : Encoding { + override val alphabet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" + } + + /** + * Base64 URL + */ + data object UrlSafe : Encoding { + override val alphabet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + } + + /** + * Base64 URL + */ + data object UrlSafePad : Encoding { + override val alphabet: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=" + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/StringExt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/StringExt.kt new file mode 100644 index 0000000..f303299 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/StringExt.kt @@ -0,0 +1,91 @@ +package io.iohk.atala.prism.didcomm.didpeer.base64 + +// Base64Standard +/** + * Encode a [String] to Base64 [String] standard encoding + * RFC 4648 Section 4 + */ +val String.base64Encoded: String + get() = Base64.encodeToString(this.encodeToByteArray(), Encoding.Standard) + +/** + * Decode a Base64 [String] standard encoded to [String]. + * RFC 4648 Section 4 + */ +val String.base64Decoded: String + get() = Base64.decode(this, Encoding.Standard).decodeToString() + +/** + * Decode a Base64 [String] standard encoded to [ByteArray]. + * RFC 4648 Section 4 + */ +val String.base64DecodedBytes: ByteArray + get() = Base64.decode(this, Encoding.Standard) + +// Standard with Padding +/** + * Encode a [String] to Base64 [String] standard encoding + * RFC 4648 Section 4 + */ +val String.base64PadEncoded: String + get() = Base64.encodeToString(this.encodeToByteArray(), Encoding.StandardPad) + +/** + * Decode a Base64 [String] standard encoded to [String]. + * RFC 4648 Section 4 + */ +val String.base64PadDecoded: String + get() = base64PadDecodedBytes.decodeToString() + +/** + * Decode a Base64 [String] standard encoded to [ByteArray]. + * RFC 4648 Section 4 + */ +val String.base64PadDecodedBytes: ByteArray + get() = Base64.decode(this, Encoding.StandardPad) + +// Base64URL +/** + * Encode a [String] to Base64 URL-safe encoded [String]. + * RFC 4648 Section 5 + * See [RFC 4648 §5](https://datatracker.ietf.org/doc/html/rfc4648#section-5) + */ +val String.base64UrlEncoded: String + get() = Base64.encodeToString(this.encodeToByteArray(), Encoding.UrlSafe) + +/** + * Decode a Base64 URL-safe encoded [String] to [String]. + * RFC 4648 Section 5 + */ +val String.base64UrlDecoded: String + get() = base64UrlDecodedBytes.decodeToString() + +/** + * Decode a Base64 URL-safe encoded [String] to [ByteArray]. + * RFC 4648 Section 5 + */ +val String.base64UrlDecodedBytes: ByteArray + get() = Base64.decode(this, Encoding.UrlSafe) + +// Base64URL with padding +/** + * Encode a [String] to Base64 URL-safe encoded [String]. + * RFC 4648 Section 5 + * See [RFC 4648 §5](https://datatracker.ietf.org/doc/html/rfc4648#section-5) + */ +val String.base64UrlPadEncoded: String + get() = Base64.encodeToString(this.encodeToByteArray(), Encoding.UrlSafePad) + +/** + * Decode a Base64 URL-safe encoded [String] to [String]. + * RFC 4648 Section 5 + */ +val String.base64UrlPadDecoded: String + get() = base64UrlPadDecodedBytes.decodeToString() + +/** + * Decode a Base64 URL-safe encoded [String] to [ByteArray]. + * RFC 4648 Section 5 + */ +val String.base64UrlPadDecodedBytes: ByteArray + get() = Base64.decode(this, Encoding.UrlSafePad) diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/JWKOKP.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/JWKOKP.kt index bbfe2c6..8edbfe7 100644 --- a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/JWKOKP.kt +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/JWKOKP.kt @@ -1,7 +1,7 @@ package io.iohk.atala.prism.didcomm.didpeer.core -import io.iohk.atala.prism.apollo.base64.base64UrlDecodedBytes -import io.iohk.atala.prism.apollo.base64.base64UrlEncoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlEncoded import io.iohk.atala.prism.didcomm.didpeer.VerificationMaterialPeerDID import io.iohk.atala.prism.didcomm.didpeer.VerificationMethodTypeAgreement import io.iohk.atala.prism.didcomm.didpeer.VerificationMethodTypeAuthentication diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multibase.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multibase.kt index c6564f4..d14e338 100644 --- a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multibase.kt +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multibase.kt @@ -1,8 +1,8 @@ package io.iohk.atala.prism.didcomm.didpeer.core -import io.iohk.atala.prism.apollo.base58.base58BtcDecodedBytes -import io.iohk.atala.prism.apollo.base58.base58BtcEncoded -import io.iohk.atala.prism.apollo.multibase.MultiBase +import io.iohk.atala.prism.didcomm.didpeer.base58.base58BtcDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base58.base58BtcEncoded +import io.iohk.atala.prism.didcomm.didpeer.multibase.MultiBase /** * Converts a byte array to a base58 multibase encoding. diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multicodec.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multicodec.kt index 0a0aab1..104150f 100644 --- a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multicodec.kt +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/Multicodec.kt @@ -1,6 +1,6 @@ package io.iohk.atala.prism.didcomm.didpeer.core -import io.iohk.atala.prism.apollo.varint.VarInt +import io.iohk.atala.prism.didcomm.didpeer.varint.VarInt import io.iohk.atala.prism.didcomm.didpeer.VerificationMethodTypeAgreement import io.iohk.atala.prism.didcomm.didpeer.VerificationMethodTypeAuthentication import io.iohk.atala.prism.didcomm.didpeer.VerificationMethodTypePeerDID diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/PeerDIDHelper.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/PeerDIDHelper.kt index 5303061..8f2b5f8 100644 --- a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/PeerDIDHelper.kt +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/PeerDIDHelper.kt @@ -2,8 +2,8 @@ package io.iohk.atala.prism.didcomm.didpeer.core -import io.iohk.atala.prism.apollo.base64.base64PadDecoded -import io.iohk.atala.prism.apollo.base64.base64UrlEncoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64PadDecoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlEncoded import io.iohk.atala.prism.didcomm.didpeer.JSON import io.iohk.atala.prism.didcomm.didpeer.OtherService import io.iohk.atala.prism.didcomm.didpeer.PeerDID diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBase.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBase.kt new file mode 100644 index 0000000..acddeb9 --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBase.kt @@ -0,0 +1,133 @@ +package io.iohk.atala.prism.didcomm.didpeer.multibase + +import io.iohk.atala.prism.didcomm.didpeer.base16.base16DecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base16.base16Encoded +import io.iohk.atala.prism.didcomm.didpeer.base16.base16UpperDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base16.base16UpperEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32DecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32Encoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexPadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexPadEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexUpperDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexUpperEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexUpperPadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32HexUpperPadEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32PadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32PadEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32UpperDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32UpperEncoded +import io.iohk.atala.prism.didcomm.didpeer.base32.base32UpperPadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base32.base32UpperPadEncoded +import io.iohk.atala.prism.didcomm.didpeer.base58.base58BtcDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base58.base58BtcEncoded +import io.iohk.atala.prism.didcomm.didpeer.base58.base58FlickrDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base58.base58FlickrEncoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64DecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64Encoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64PadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64PadEncoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlEncoded +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlPadDecodedBytes +import io.iohk.atala.prism.didcomm.didpeer.base64.base64UrlPadEncoded + +/** + * MultiBase Implementation of [multibase](https://github.com/multiformats/multibase) -self identifying base encodings- in KMM + */ +final object MultiBase { + + fun encode(base: Base, data: String): String { + return encode(base, data.encodeToByteArray()) + } + + fun encode(base: Base, data: ByteArray): String { + return when (base) { + Base.BASE16 -> "${base.prefix}${data.base16Encoded}" + Base.BASE16_UPPER -> "${base.prefix}${data.base16UpperEncoded}" + + Base.BASE32 -> "${base.prefix}${data.base32Encoded}" + Base.BASE32_UPPER -> "${base.prefix}${data.base32UpperEncoded}" + Base.BASE32_PAD -> "${base.prefix}${data.base32PadEncoded}" + Base.BASE32_UPPER_PAD -> "${base.prefix}${data.base32UpperPadEncoded}" + Base.BASE32_HEX -> "${base.prefix}${data.base32HexEncoded}" + Base.BASE32_HEX_UPPER -> "${base.prefix}${data.base32HexUpperEncoded}" + Base.BASE32_HEX_PAD -> "${base.prefix}${data.base32HexPadEncoded}" + Base.BASE32_HEX_UPPER_PAD -> "${base.prefix}${data.base32HexUpperPadEncoded}" + + Base.BASE58_FLICKR -> "${base.prefix}${data.base58FlickrEncoded}" + Base.BASE58_BTC -> "${base.prefix}${data.base58BtcEncoded}" + + Base.BASE64 -> "${base.prefix}${data.base64Encoded}" + Base.BASE64_URL -> "${base.prefix}${data.base64UrlEncoded}" + Base.BASE64_PAD -> "${base.prefix}${data.base64PadEncoded}" + Base.BASE64_URL_PAD -> "${base.prefix}${data.base64UrlPadEncoded}" + } + } + + @Throws(IllegalStateException::class) + fun decode(data: String): ByteArray { + val prefix = data[0] + val rest = data.substring(1) + return when (Base.lookup(prefix)) { + Base.BASE16 -> rest.base16DecodedBytes + Base.BASE16_UPPER -> rest.base16UpperDecodedBytes + + Base.BASE32 -> rest.base32DecodedBytes + Base.BASE32_UPPER -> rest.base32UpperDecodedBytes + Base.BASE32_PAD -> rest.base32PadDecodedBytes + Base.BASE32_UPPER_PAD -> rest.base32UpperPadDecodedBytes + Base.BASE32_HEX -> rest.base32HexDecodedBytes + Base.BASE32_HEX_UPPER -> rest.base32HexUpperDecodedBytes + Base.BASE32_HEX_PAD -> rest.base32HexPadDecodedBytes + Base.BASE32_HEX_UPPER_PAD -> rest.base32HexUpperPadDecodedBytes + + Base.BASE58_FLICKR -> rest.base58FlickrDecodedBytes + Base.BASE58_BTC -> rest.base58BtcDecodedBytes + + Base.BASE64 -> rest.base64DecodedBytes + Base.BASE64_URL -> rest.base64UrlDecodedBytes + Base.BASE64_PAD -> rest.base64PadDecodedBytes + Base.BASE64_URL_PAD -> rest.base64UrlPadDecodedBytes + } + } + + enum class Base(val prefix: Char) { + BASE16('f'), + BASE16_UPPER('F'), + + BASE32('b'), + BASE32_UPPER('B'), + BASE32_PAD('c'), + BASE32_UPPER_PAD('C'), + BASE32_HEX('v'), + BASE32_HEX_UPPER('V'), + BASE32_HEX_PAD('t'), + BASE32_HEX_UPPER_PAD('T'), + + BASE58_FLICKR('Z'), + BASE58_BTC('z'), + + BASE64('m'), + BASE64_URL('u'), + BASE64_PAD('M'), + BASE64_URL_PAD('U'); + + companion object { + private val baseMap: MutableMap = mutableMapOf() + + init { + for (base in values()) { + baseMap[base.prefix] = base + } + } + + @Throws(IllegalStateException::class) + fun lookup(prefix: Char): Base { + return baseMap[prefix] + ?: throw IllegalStateException("Unknown Multibase type: $prefix") + } + } + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarInt.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarInt.kt new file mode 100644 index 0000000..5f578ad --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarInt.kt @@ -0,0 +1,29 @@ +package io.iohk.atala.prism.didcomm.didpeer.varint + +import okio.Buffer + +object VarInt : VarIntInterface { + override fun write(value: Int, byteBuffer: Buffer) { + var value = value + while ((value and -0x80).toLong() != 0L) { + byteBuffer.writeByte(value and 0x7F or 0x80) + value = value ushr 7 + } + byteBuffer.writeByte(value and 0x7F) + } + + override fun read(byteBuffer: Buffer): Int { + var value = 0 + var i = 0 + var b = 0 + while (byteBuffer.size > 0 && byteBuffer.readByte().toInt().also { b = it } and 0x80 != 0) { + value = value or (b and 0x7F shl i) + i += 7 + if (i > 35) { + throw IllegalArgumentException("Variable length quantity is too long") + } + } + value = value or (b shl i) + return value + } +} diff --git a/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntInterface.kt b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntInterface.kt new file mode 100644 index 0000000..15b2d0a --- /dev/null +++ b/didpeer/src/commonMain/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntInterface.kt @@ -0,0 +1,8 @@ +package io.iohk.atala.prism.didcomm.didpeer.varint + +import okio.Buffer + +interface VarIntInterface { + fun write(value: Int, byteBuffer: Buffer) + fun read(byteBuffer: Buffer): Int +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16Tests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16Tests.kt new file mode 100644 index 0000000..39a04c5 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base16/Base16Tests.kt @@ -0,0 +1,62 @@ +package io.iohk.atala.prism.didcomm.didpeer.base16 + +import kotlin.test.Test +import kotlin.test.assertEquals + +class Base16Tests { + + @Test + fun testEncodeBase16() { + assertEquals("57656C636F6D6520746F20494F4721".lowercase(), "Welcome to IOG!".base16Encoded) + } + + @Test + fun testDecodeBase16() { + assertEquals("Welcome to IOG!", "57656C636F6D6520746F20494F4721".lowercase().base16Decoded) + } + + @Test + fun testEncodeBase16Upper() { + assertEquals("57656C636F6D6520746F20494F4721", "Welcome to IOG!".base16UpperEncoded) + } + + @Test + fun testDecodeBase16Upper() { + assertEquals("Welcome to IOG!", "57656C636F6D6520746F20494F4721".base16UpperDecoded) + } + + @Test + fun testEncodeBase16RFC_4648() { + assertEquals("", "".base16Encoded) + } + + @Test + fun testEncodeBase16RFC_4648_1() { + assertEquals("66", "f".base16Encoded) + } + + @Test + fun testEncodeBase16RFC_4648_2() { + assertEquals("666F", "fo".base16Encoded.uppercase()) + } + + @Test + fun testEncodeBase16RFC_4648_3() { + assertEquals("666F6F", "foo".base16Encoded.uppercase()) + } + + @Test + fun testEncodeBase16RFC_4648_4() { + assertEquals("666F6F62", "foob".base16Encoded.uppercase()) + } + + @Test + fun testEncodeBase16RFC_4648_5() { + assertEquals("666F6F6261", "fooba".base16Encoded.uppercase()) + } + + @Test + fun testEncodeBase16RFC_4648_6() { + assertEquals("666F6F626172", "foobar".base16Encoded.uppercase()) + } +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32Tests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32Tests.kt new file mode 100644 index 0000000..9d45678 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base32/Base32Tests.kt @@ -0,0 +1,127 @@ +package io.iohk.atala.prism.didcomm.didpeer.base32 + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class Base32Tests { + @Test + fun testEncodeBase32() { + assertEquals("K5SWYY3PNVSSA5DPEBEU6RZB".lowercase(), "Welcome to IOG!".base32Encoded) + } + + @Test + fun testDecodeBase32() { + assertEquals("Welcome to IOG!", "K5SWYY3PNVSSA5DPEBEU6RZB".lowercase().base32Decoded) + } + + @Test + fun testEncodeBase32WithPadding() { + assertEquals("K5SWYY3PNVSSA5DPEBEU6RZB".lowercase(), "Welcome to IOG!".base32PadEncoded) + } + + @Test + fun testDecodeBase32WithPadding() { + assertEquals("Welcome to IOG!", "K5SWYY3PNVSSA5DPEBEU6RZB".lowercase().base32PadDecoded) + } + + @Test + fun testEncodeBase32Upper() { + assertEquals("K5SWYY3PNVSSA5DPEBEU6RZB", "Welcome to IOG!".base32UpperEncoded) + } + + @Test + fun testDecodeBase32Upper() { + assertEquals("Welcome to IOG!", "K5SWYY3PNVSSA5DPEBEU6RZB".base32UpperDecoded) + } + + @Test + fun testEncodeBase32UpperWithPadding() { + "".encodeToByteArray() + assertEquals("K5SWYY3PNVSSA5DPEBEU6RZB", "Welcome to IOG!".base32UpperPadEncoded) + } + + @Test + fun testDecodeBase32UpperWithPadding() { + assertEquals("Welcome to IOG!", "K5SWYY3PNVSSA5DPEBEU6RZB".base32UpperPadDecoded) + } + + @Test + fun testDecodeBase32HexUpper() { + // Hex value of "Welcome to IOG!" in hex + val expected = byteArrayOf(1, 17, -107, -115, -107, -71, -47, -55, -123, -79, -91, -23, -108, -127, -107, -39, -107, -55, -27, -47, -95, -91, -71, -100, -124, -124, -124) + assertContentEquals(expected, "8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144".uppercase().base32HexUpperDecodedBytes) + } + + @Test + fun testDecodeBase32HexUpperWithPadding() { + // Hex value of "Welcome to IOG!" in hex + val expected = byteArrayOf(1, 17, -107, -115, -107, -71, -47, -55, -123, -79, -91, -23, -108, -127, -107, -39, -107, -55, -27, -47, -95, -91, -71, -100, -124, -124, -124) + assertContentEquals(expected, "8him6pbeehp62r39f9ii0pbmclp7it38d5n6e89144".uppercase().base32HexUpperPadDecodedBytes) + } + + @Test + fun testEncodeBase32_RFC_4648_1() { + assertEquals("MZXW6===", "foo".base32UpperPadEncoded) + assertEquals("foo", "MZXW6===".base32UpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_2() { + assertEquals("MZXW6YQ=", "foob".base32UpperPadEncoded) + assertEquals("foob", "MZXW6YQ=".base32UpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_3() { + assertEquals("MZXW6YTB", "fooba".base32UpperPadEncoded) + assertEquals("fooba", "MZXW6YTB".base32UpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_4() { + assertEquals("MZXW6YTBOI======", "foobar".base32UpperPadEncoded) + assertEquals("foobar", "MZXW6YTBOI======".base32UpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_5() { + assertEquals("", "".base32Encoded) + } + + @Test + fun testEncodeBase32_RFC_4648_6() { + assertEquals("CO======", "f".base32HexUpperPadEncoded) + assertEquals("f", "CO======".base32HexUpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_7() { + assertEquals("CPNG====", "fo".base32HexUpperPadEncoded) + assertEquals("fo", "CPNG====".base32HexUpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_8() { + assertEquals("CPNMU===", "foo".base32HexUpperPadEncoded) + assertEquals("foo", "CPNMU===".base32HexUpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_9() { + assertEquals("CPNMUOG=", "foob".base32HexUpperPadEncoded) + assertEquals("foob", "CPNMUOG=".base32HexUpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_10() { + assertEquals("CPNMUOJ1", "fooba".base32HexUpperPadEncoded) + assertEquals("fooba", "CPNMUOJ1".base32HexUpperPadDecoded) + } + + @Test + fun testEncodeBase32_RFC_4648_11() { + assertEquals("CPNMUOJ1E8======", "foobar".base32HexUpperPadEncoded) + assertEquals("foobar", "CPNMUOJ1E8======".base32HexUpperPadDecoded) + } +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58Tests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58Tests.kt new file mode 100644 index 0000000..b05eb79 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base58/Base58Tests.kt @@ -0,0 +1,26 @@ +package io.iohk.atala.prism.didcomm.didpeer.base58 + +import kotlin.test.Test +import kotlin.test.assertEquals + +class Base58Tests { + @Test + fun testEncodeBase58BTC() { + assertEquals("Z8XgFYKj8dyvwswt51Y", "Welcome to IOG".base58BtcEncoded) + } + + @Test + fun testDecodeBase58BTC() { + assertEquals("Welcome to IOG", "Z8XgFYKj8dyvwswt51Y".base58BtcDecoded) + } + + @Test + fun testEncodeBase58Flickr() { + assertEquals("y8wFfxjJ8CYVWSWT51x", "Welcome to IOG".base58FlickrEncoded) + } + + @Test + fun testDecodeBase58Flickr() { + assertEquals("Welcome to IOG", "y8wFfxjJ8CYVWSWT51x".base58FlickrDecoded) + } +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64Tests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64Tests.kt new file mode 100644 index 0000000..f2089f5 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/base64/Base64Tests.kt @@ -0,0 +1,194 @@ +package io.iohk.atala.prism.didcomm.didpeer.base64 + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class Base64Tests { + // Extension + @Test + fun byteArray_asCharArray() { + assertContentEquals( + charArrayOf('"', 'F', ' ', 'ᅣ', '_', '\'', '!', Char.MIN_VALUE, '�', 'E', 'ホ', '￘', 'モ', 'ᆰ', '￶', '│'), + byteArrayOf(34, 70, 9, -60, 95, 39, 33, 0, -3, 69, -114, -40, -109, -86, -10, -24).asCharArray() + ) + } + + // Base64Standard + @Test + fun byteArray_base64Decoded() { + assertEquals("Hello, world!", "SGVsbG8sIHdvcmxkIQ==".base64PadDecoded) + assertContentEquals( + byteArrayOf( + -94, 124, -26, -112, -72, -84, 16, 11, 67, -45, 107, 38, -99, 79, 62, -49, 83, 26, -85, -70, -122, 53, + 67, 42, -94, -87, 61, -74, 66, 0, 80, -125, -17, -11, -125, 63, 109, -15, 56, -95, -33, 18, 110, 47, + 47, -20, -72, -34, 53, -69, 49, -45, 54, 53, -21, 43, 9, -84, -125, 72, -61, 76, 31, -46 + ), + "onzmkLisEAtD02smnU8+z1Maq7qGNUMqoqk9tkIAUIPv9YM/bfE4od8Sbi8v7LjeNbsx0zY16ysJrINIw0wf0g==".base64PadDecodedBytes + ) + } + + @Test + fun byteArray_base64Encoded() { + assertEquals( + "xvrp9DBWlei2mG0ov9MN+A==", // value1 + byteArrayOf(-58, -6, -23, -12, 48, 86, -107, -24, -74, -104, 109, 40, -65, -45, 13, -8).base64PadEncoded + ) + assertEquals( + "IkYJxF8nIQD9RY7Yk6r26A==", // value222 + byteArrayOf(34, 70, 9, -60, 95, 39, 33, 0, -3, 69, -114, -40, -109, -86, -10, -24).base64PadEncoded + ) + assertEquals( + "U0GeVBi2dNcdL2IO0nJo5Q==", // value555 + byteArrayOf(83, 65, -98, 84, 24, -74, 116, -41, 29, 47, 98, 14, -46, 114, 104, -27).base64PadEncoded + ) + } + + @Test + fun string_base64Decoded() { + assertEquals("word", "d29yZA==".base64PadDecoded) + assertEquals("Word", "V29yZA==".base64PadDecoded) + assertEquals("Hello", "SGVsbG8=".base64PadDecoded) + assertEquals("World!", "V29ybGQh".base64PadDecoded) + assertEquals("Hello, world!", "SGVsbG8sIHdvcmxkIQ==".base64PadDecoded) + assertEquals( + Encoding.Standard.alphabet, + "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==".base64PadDecoded + ) + assertEquals("abcd", "YWJjZA==".base64PadDecoded) + assertEquals("saschpe", "c2FzY2hwZQ==".base64PadDecoded) + assertEquals( + "1234567890-=!@#\$%^&*()_+qwertyuiop[];'\\,./?><|\":}{P`~", + "MTIzNDU2Nzg5MC09IUAjJCVeJiooKV8rcXdlcnR5dWlvcFtdOydcLC4vPz48fCI6fXtQYH4=".base64PadDecoded + ) + } + + @Test + fun string_base64Encoded() { + assertEquals("d29yZA==", "word".base64PadEncoded) + assertEquals("V29yZA==", "Word".base64PadEncoded) + assertEquals("SGVsbG8=", "Hello".base64PadEncoded) + assertEquals("V29ybGQh", "World!".base64PadEncoded) + assertEquals("SGVsbG8sIHdvcmxkIQ==", "Hello, world!".base64PadEncoded) + assertEquals("SGVsbG8sIHdvcmxkIQ==", "Hello, world!".encodeToByteArray().base64PadEncoded) + assertEquals( + "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==", + Encoding.Standard.alphabet.base64PadEncoded + ) + assertEquals("YWJjZA==", "abcd".base64PadEncoded) + assertEquals("c2FzY2hwZQ==", "saschpe".base64PadEncoded) + assertEquals( + "MTIzNDU2Nzg5MC09IUAjJCVeJiooKV8rcXdlcnR5dWlvcFtdOydcLC4vPz48fCI6fXtQYH4=", + "1234567890-=!@#\$%^&*()_+qwertyuiop[];'\\,./?><|\":}{P`~".base64PadEncoded + ) + } + + // Base64Url + @Test + fun byteArray_base64UrlDecoded() { + assertEquals("Hello, world!", "SGVsbG8sIHdvcmxkIQ==".base64UrlPadDecoded) + assertContentEquals( + byteArrayOf( + -94, 124, -26, -112, -72, -84, 16, 11, 67, -45, 107, 38, -99, 79, 62, -49, 83, 26, -85, -70, -122, 53, + 67, 42, -94, -87, 61, -74, 66, 0, 80, -125, -17, -11, -125, 63, 109, -15, 56, -95, -33, 18, 110, 47, + 47, -20, -72, -34, 53, -69, 49, -45, 54, 53, -21, 43, 9, -84, -125, 72, -61, 76, 31, -46 + ), + "onzmkLisEAtD02smnU8-z1Maq7qGNUMqoqk9tkIAUIPv9YM_bfE4od8Sbi8v7LjeNbsx0zY16ysJrINIw0wf0g==".base64UrlPadDecodedBytes + ) + } + + @Test + fun byteArray_base64UrlEncoded() { + assertEquals( + "xvrp9DBWlei2mG0ov9MN-A", // value1 + byteArrayOf(-58, -6, -23, -12, 48, 86, -107, -24, -74, -104, 109, 40, -65, -45, 13, -8).base64UrlEncoded + ) + assertEquals( + "IkYJxF8nIQD9RY7Yk6r26A", // value222 + byteArrayOf(34, 70, 9, -60, 95, 39, 33, 0, -3, 69, -114, -40, -109, -86, -10, -24).base64UrlEncoded + ) + assertEquals( + "U0GeVBi2dNcdL2IO0nJo5Q", // value555 + byteArrayOf(83, 65, -98, 84, 24, -74, 116, -41, 29, 47, 98, 14, -46, 114, 104, -27).base64UrlEncoded + ) + } + + @Test + fun string_base64UrlDecoded() { + assertEquals("word", "d29yZA==".base64UrlDecoded) + assertEquals("Word", "V29yZA==".base64UrlDecoded) + assertEquals("Hello", "SGVsbG8=".base64UrlDecoded) + assertEquals("World!", "V29ybGQh".base64UrlDecoded) + assertEquals("Hello, world!", "SGVsbG8sIHdvcmxkIQ".base64UrlDecoded) + assertEquals("Hello, world!", "SGVsbG8sIHdvcmxkIQ==".base64UrlPadDecoded) + assertEquals( + Encoding.Standard.alphabet, + "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw==".base64UrlPadDecoded + ) + assertEquals("Salt", "U2FsdA==".base64UrlPadDecoded) + assertEquals("Pepper", "UGVwcGVy".base64UrlPadDecoded) + assertEquals("abcd", "YWJjZA".base64UrlPadDecoded) + assertEquals( + "1234567890-=!@#\$%^&*()_+qwertyuiop[];'\\,./?><|\":}{P`~", + "MTIzNDU2Nzg5MC09IUAjJCVeJiooKV8rcXdlcnR5dWlvcFtdOydcLC4vPz48fCI6fXtQYH4".base64UrlDecoded + ) + assertEquals("saschpe", "c2FzY2hwZQ".base64UrlDecoded) + } + + @Test + fun string_base64UrlEncoded() { + assertEquals("d29yZA", "word".base64UrlEncoded) + assertEquals("V29yZA", "Word".base64UrlEncoded) + assertEquals("SGVsbG8", "Hello".base64UrlEncoded) + assertEquals("V29ybGQh", "World!".base64UrlEncoded) + assertEquals("SGVsbG8sIHdvcmxkIQ", "Hello, world!".base64UrlEncoded) + assertEquals("SGVsbG8sIHdvcmxkIQ", "Hello, world!".encodeToByteArray().base64UrlEncoded) + assertEquals( + "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLw", + Encoding.Standard.alphabet.base64UrlEncoded + ) + assertEquals("U2FsdA", "Salt".base64UrlEncoded) + assertEquals("UGVwcGVy", "Pepper".base64UrlEncoded) + assertEquals("YWJjZA", "abcd".base64UrlEncoded) + assertEquals( + "MTIzNDU2Nzg5MC09IUAjJCVeJiooKV8rcXdlcnR5dWlvcFtdOydcLC4vPz48fCI6fXtQYH4", + "1234567890-=!@#\$%^&*()_+qwertyuiop[];'\\,./?><|\":}{P`~".base64UrlEncoded + ) + assertEquals("c2FzY2hwZQ", "saschpe".base64UrlEncoded) + } + + @Test + fun testEncodeBase16RFC_4648() { + assertEquals("", "".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_1() { + assertEquals("Zg==", "f".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_2() { + assertEquals("Zm8=", "fo".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_3() { + assertEquals("Zm9v", "foo".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_4() { + assertEquals("Zm9vYg==", "foob".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_5() { + assertEquals("Zm9vYmE=", "fooba".base64PadEncoded) + } + + @Test + fun testEncodeBase16RFC_4648_6() { + assertEquals("Zm9vYmFy", "foobar".base64PadEncoded) + } +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/VarIntTests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/VarIntTests.kt index 180821e..4ba00be 100644 --- a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/VarIntTests.kt +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/core/VarIntTests.kt @@ -1,6 +1,6 @@ package io.iohk.atala.prism.didcomm.didpeer.core -import io.iohk.atala.prism.apollo.varint.VarInt +import io.iohk.atala.prism.didcomm.didpeer.varint.VarInt import okio.Buffer import kotlin.test.Test import kotlin.test.assertEquals @@ -23,51 +23,4 @@ class VarIntTests { val fin: Int = VarInt.read(byteBuffer) assertEquals(origin, fin) } - -// @Test -// fun test() { -// val encryptionKeys = listOf( -// VerificationMaterialAgreement( -// type = VerificationMethodTypeAgreement.X25519_KEY_AGREEMENT_KEY_2019, -// format = VerificationMaterialFormatPeerDID.BASE58, -// value = "DmgBSHMqaZiYqwNMEJJuxWzsGGC8jUYADrfSdBrC6L8s", -// ) -// ) -// val signingKeys = listOf( -// VerificationMaterialAuthentication( -// type = VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2018, -// format = VerificationMaterialFormatPeerDID.BASE58, -// value = "ByHnpUCFb1vAfh9CFZ8ZkmUZguURW8nSw889hy6rD8L7", -// ) -// ) -// val signingKeys2 = listOf( -// VerificationMaterialAuthentication( -// type = VerificationMethodTypeAuthentication.ED25519_VERIFICATION_KEY_2020, -// format = VerificationMaterialFormatPeerDID.MULTIBASE, -// value = "zrv3hVor1kqRxzN51GT7Lt7C67km8v6TNStM8xx1wBzMjPgb9gCHyULbWTCM75Jf8Z4d7oH24z3D2bUwwNNx6FfLcLQ", -// ) -// ) -// val service = -// """ -// { -// "type": "DIDCommMessaging", -// "serviceEndpoint": "https://example.com/endpoint1", -// "routingKeys": ["did:example:somemediator#somekey1"], -// "accept": ["didcomm/v2", "didcomm/aip2;env=rfc587"] -// } -// """ -// -// val peerDIDAlgo0 = createPeerDIDNumalgo0(signingKeys[0]) -// -// val peerDIDAlgo02 = createPeerDIDNumalgo0(signingKeys2[0]) -// -// val didDocAlgo0Json = resolvePeerDID(peerDIDAlgo0) -// val didDocAlgo0 = DIDDocPeerDID.fromJson(didDocAlgo0Json) -// -// val peerDIDAlgo2 = createPeerDIDNumalgo2( -// encryptionKeys, signingKeys, service -// ) -// val didDocAlgo2Json = resolvePeerDID(peerDIDAlgo2) -// val didDocAlgo2 = DIDDocPeerDID.fromJson(didDocAlgo2Json) -// } } diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBaseTests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBaseTests.kt new file mode 100644 index 0000000..9328ab2 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/multibase/MultiBaseTests.kt @@ -0,0 +1,16 @@ +package io.iohk.atala.prism.didcomm.didpeer.multibase + +import kotlin.test.Test +import kotlin.test.assertEquals + +// TODO: add the rest of unit test +class MultiBaseTests { + @Test + fun testMultibaseBase16() { + assertEquals("f57656c636f6d6520746f20494f4721", MultiBase.encode(MultiBase.Base.BASE16, "Welcome to IOG!")) + assertEquals("Welcome to IOG!", MultiBase.decode("f57656c636f6d6520746f20494f4721").decodeToString()) + + assertEquals("F57656C636F6D6520746F20494F4721", MultiBase.encode(MultiBase.Base.BASE16_UPPER, "Welcome to IOG!")) + assertEquals("Welcome to IOG!", MultiBase.decode("F57656C636F6D6520746F20494F4721").decodeToString()) + } +} diff --git a/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntTests.kt b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntTests.kt new file mode 100644 index 0000000..4af64c7 --- /dev/null +++ b/didpeer/src/commonTest/kotlin/io/iohk/atala/prism/didcomm/didpeer/varint/VarIntTests.kt @@ -0,0 +1,16 @@ +package io.iohk.atala.prism.didcomm.didpeer.varint + +import okio.Buffer +import kotlin.test.Test +import kotlin.test.assertEquals + +class VarIntTests { + @Test + fun testVarInt() { + val origin = 1234774 + val byteBuffer = Buffer() + VarInt.write(origin, byteBuffer) + val value: Int = VarInt.read(byteBuffer) + assertEquals(origin, value) + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 41dfb87..e411586 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists