From f1e2deedcca6b194760f9281ad94f61cf43381a9 Mon Sep 17 00:00:00 2001 From: Ahmed Moussa Date: Fri, 24 Nov 2023 14:46:51 +0200 Subject: [PATCH] feat: moving KMP to one repo Signed-off-by: Ahmed Moussa --- .github/workflows/lint-pr.yml | 16 + .github/workflows/pull-request.yml | 84 ++ .github/workflows/release.yml | 59 +- .gitignore | 13 +- anoncred-kmm/.editorconfig | 10 + anoncred-kmm/.gitignore | 8 + anoncred-kmm/.mega-linter.yml | 28 + anoncred-kmm/README.md | 53 ++ .../anoncred-wrapper-rust/.cargo/config.toml | 12 + anoncred-kmm/anoncred-wrapper-rust/.gitignore | 15 + anoncred-kmm/anoncred-wrapper-rust/Cargo.toml | 45 ++ .../anoncred-wrapper-rust/build.gradle.kts | 640 +++++++++++++++ anoncred-kmm/anoncred-wrapper-rust/build.rs | 14 + anoncred-kmm/anoncreds-kmp/build.gradle.kts | 295 +++++++ .../src/androidMain/AndroidManifest.xml | 2 + .../atala/prism/anoncred/AndroidIgnore.kt | 8 + .../io/iohk/atala/prism/anoncred/Platform.kt | 10 + .../atala/prism/anoncred/AndroidIgnore.kt | 6 + .../atala/prism/anoncred/PrismIssuerTests.kt | 226 ++++++ .../atala/prism/anoncred/PrismProverTests.kt | 54 ++ .../atala/prism/anoncred/AndroidIgnore.kt | 6 + .../src/nativeInterop/cinterop/anoncreds.def | 1 + .../cinterop/anoncreds_wrapper.def | 1 + anoncred-kmm/build.gradle.kts | 52 ++ anoncred-kmm/gradle.properties | 12 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 61608 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + anoncred-kmm/gradlew | 244 ++++++ anoncred-kmm/gradlew.bat | 92 +++ anoncred-kmm/settings.gradle.kts | 5 + anoncred-kmm/testapp/.gitignore | 1 + anoncred-kmm/testapp/build.gradle.kts | 58 ++ anoncred-kmm/testapp/proguard-rules.pro | 21 + .../testapp/ExampleInstrumentedTest.kt | 22 + .../com/example/testapp/PrismIssuerTests.kt | 225 ++++++ .../com/example/testapp/PrismProverTests.kt | 56 ++ .../testapp/src/main/AndroidManifest.xml | 22 + .../java/com/example/testapp/MainActivity.kt | 19 + .../res/drawable/ic_launcher_background.xml | 170 ++++ .../res/drawable/ic_launcher_foreground.xml | 30 + .../src/main/res/layout/activity_main.xml | 18 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../src/main/res/values-night/themes.xml | 7 + .../testapp/src/main/res/values/colors.xml | 5 + .../testapp/src/main/res/values/strings.xml | 3 + .../testapp/src/main/res/values/themes.xml | 9 + .../com/example/testapp/ExampleUnitTest.kt | 16 + anoncred-kmm/uniffi-kmm/Cargo.toml | 19 + anoncred-kmm/uniffi-kmm/askama.toml | 8 + anoncred-kmm/uniffi-kmm/build.gradle.kts | 17 + .../src/androidMain/kotlin/ForeignBytes.kt | 11 + .../androidMain/kotlin/ForeignCallbackJvm.kt | 19 + .../src/androidMain/kotlin/Pointer.kt | 5 + .../androidMain/kotlin/RustCallStatusJvm.kt | 22 + .../src/commonMain/kotlin/Disposable.kt | 20 + .../src/commonMain/kotlin/FFIObject.kt | 40 + .../src/commonMain/kotlin/FfiConverter.kt | 28 + .../commonMain/kotlin/FfiConverterBoolean.kt | 15 + .../src/commonMain/kotlin/FfiConverterByte.kt | 15 + .../kotlin/FfiConverterCallbackInterface.kt | 72 ++ .../commonMain/kotlin/FfiConverterDouble.kt | 15 + .../commonMain/kotlin/FfiConverterDuration.kt | 26 + .../commonMain/kotlin/FfiConverterFloat.kt | 15 + .../commonMain/kotlin/FfiConverterInstant.kt | 27 + .../src/commonMain/kotlin/FfiConverterInt.kt | 15 + .../src/commonMain/kotlin/FfiConverterLong.kt | 15 + .../kotlin/FfiConverterRustBuffer.kt | 6 + .../commonMain/kotlin/FfiConverterShort.kt | 15 + .../commonMain/kotlin/FfiConverterString.kt | 35 + .../commonMain/kotlin/FfiConverterUByte.kt | 15 + .../src/commonMain/kotlin/FfiConverterUInt.kt | 15 + .../commonMain/kotlin/FfiConverterULong.kt | 15 + .../commonMain/kotlin/FfiConverterUShort.kt | 15 + .../src/commonMain/kotlin/ForeignBytes.kt | 3 + .../src/commonMain/kotlin/ForeignCallback.kt | 4 + .../commonMain/kotlin/InternalException.kt | 1 + .../src/commonMain/kotlin/Pointer.kt | 7 + .../src/commonMain/kotlin/RustBuffer.kt | 20 + .../src/commonMain/kotlin/RustCallStatus.kt | 15 + .../src/commonMain/kotlin/rustCall.kt | 34 + .../callback_interface.rs | 33 + .../src/gen_kotlin_multiplatform/compounds.rs | 94 +++ .../src/gen_kotlin_multiplatform/custom.rs | 29 + .../src/gen_kotlin_multiplatform/enum_.rs | 37 + .../src/gen_kotlin_multiplatform/error.rs | 29 + .../src/gen_kotlin_multiplatform/external.rs | 29 + .../gen_kotlin_multiplatform/miscellany.rs | 32 + .../src/gen_kotlin_multiplatform/mod.rs | 739 ++++++++++++++++++ .../src/gen_kotlin_multiplatform/object.rs | 29 + .../gen_kotlin_multiplatform/primitives.rs | 79 ++ .../src/gen_kotlin_multiplatform/record.rs | 29 + .../src/jvmMain/kotlin/ForeignBytes.kt | 11 + .../src/jvmMain/kotlin/ForeignCallbackJvm.kt | 19 + .../uniffi-kmm/src/jvmMain/kotlin/Pointer.kt | 5 + .../src/jvmMain/kotlin/RustCallStatusJvm.kt | 22 + anoncred-kmm/uniffi-kmm/src/lib.rs | 137 ++++ .../src/nativeInterop/cinterop/uniffi.def | 2 + .../nativeInterop/cinterop/uniffi/uniffi.h | 62 ++ .../src/nativeMain/kotlin/ForeignCallback.kt | 7 + .../src/nativeMain/kotlin/Pointer.kt | 11 + .../android/CallbackInterfaceTemplate.kt.j2 | 2 + .../android/RustBufferTemplate.kt.j2 | 54 ++ .../templates/android/UniFFILibTemplate.kt.j2 | 35 + .../common/CallbackInterfaceTemplate.kt.j2 | 104 +++ .../templates/common/CustomTypeTemplate.kt.j2 | 54 ++ .../src/templates/common/EnumTemplate.kt.j2 | 100 +++ .../src/templates/common/ErrorTemplate.kt.j2 | 105 +++ .../src/templates/common/MapTemplate.kt.j2 | 33 + .../src/templates/common/ObjectTemplate.kt.j2 | 86 ++ .../templates/common/OptionalTemplate.kt.j2 | 27 + .../src/templates/common/RecordTemplate.kt.j2 | 42 + .../templates/common/SequenceTemplate.kt.j2 | 24 + .../common/TopLevelFunctionsTemplate.kt.j2 | 21 + .../templates/common/UniFFILibTemplate.kt.j2 | 10 + .../headers/BridgingHeaderTemplate.h.j2 | 55 ++ .../uniffi-kmm/src/templates/helpers.j2 | 81 ++ .../jvm/CallbackInterfaceTemplate.kt.j2 | 2 + .../templates/jvm/RustBufferTemplate.kt.j2 | 54 ++ .../src/templates/jvm/UniFFILibTemplate.kt.j2 | 34 + .../native/CallbackInterfaceTemplate.kt.j2 | 6 + .../native/ForeignBytesTemplate.kt.j2 | 5 + .../templates/native/RustBufferTemplate.kt.j2 | 46 ++ .../native/RustCallStatusTemplate.kt.j2 | 16 + .../templates/native/UniFFILibTemplate.kt.j2 | 22 + uniffi/src/anoncreds.udl | 2 +- 136 files changed, 5766 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/lint-pr.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 anoncred-kmm/.editorconfig create mode 100644 anoncred-kmm/.gitignore create mode 100644 anoncred-kmm/.mega-linter.yml create mode 100644 anoncred-kmm/README.md create mode 100644 anoncred-kmm/anoncred-wrapper-rust/.cargo/config.toml create mode 100644 anoncred-kmm/anoncred-wrapper-rust/.gitignore create mode 100644 anoncred-kmm/anoncred-wrapper-rust/Cargo.toml create mode 100644 anoncred-kmm/anoncred-wrapper-rust/build.gradle.kts create mode 100644 anoncred-kmm/anoncred-wrapper-rust/build.rs create mode 100644 anoncred-kmm/anoncreds-kmp/build.gradle.kts create mode 100644 anoncred-kmm/anoncreds-kmp/src/androidMain/AndroidManifest.xml create mode 100644 anoncred-kmm/anoncreds-kmp/src/androidUnitTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/commonMain/kotlin/io/iohk/atala/prism/anoncred/Platform.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismIssuerTests.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismProverTests.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/jvmTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt create mode 100644 anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds.def create mode 100644 anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds_wrapper.def create mode 100644 anoncred-kmm/build.gradle.kts create mode 100644 anoncred-kmm/gradle.properties create mode 100644 anoncred-kmm/gradle/wrapper/gradle-wrapper.jar create mode 100644 anoncred-kmm/gradle/wrapper/gradle-wrapper.properties create mode 100755 anoncred-kmm/gradlew create mode 100644 anoncred-kmm/gradlew.bat create mode 100644 anoncred-kmm/settings.gradle.kts create mode 100644 anoncred-kmm/testapp/.gitignore create mode 100644 anoncred-kmm/testapp/build.gradle.kts create mode 100644 anoncred-kmm/testapp/proguard-rules.pro create mode 100644 anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/ExampleInstrumentedTest.kt create mode 100644 anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismIssuerTests.kt create mode 100644 anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismProverTests.kt create mode 100644 anoncred-kmm/testapp/src/main/AndroidManifest.xml create mode 100644 anoncred-kmm/testapp/src/main/java/com/example/testapp/MainActivity.kt create mode 100644 anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_background.xml create mode 100644 anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 anoncred-kmm/testapp/src/main/res/layout/activity_main.xml create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 anoncred-kmm/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 anoncred-kmm/testapp/src/main/res/values-night/themes.xml create mode 100644 anoncred-kmm/testapp/src/main/res/values/colors.xml create mode 100644 anoncred-kmm/testapp/src/main/res/values/strings.xml create mode 100644 anoncred-kmm/testapp/src/main/res/values/themes.xml create mode 100644 anoncred-kmm/testapp/src/test/java/com/example/testapp/ExampleUnitTest.kt create mode 100644 anoncred-kmm/uniffi-kmm/Cargo.toml create mode 100644 anoncred-kmm/uniffi-kmm/askama.toml create mode 100644 anoncred-kmm/uniffi-kmm/build.gradle.kts create mode 100644 anoncred-kmm/uniffi-kmm/src/androidMain/kotlin/ForeignBytes.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/androidMain/kotlin/ForeignCallbackJvm.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/androidMain/kotlin/Pointer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/androidMain/kotlin/RustCallStatusJvm.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/Disposable.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FFIObject.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverter.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterBoolean.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterByte.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterCallbackInterface.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterDouble.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterDuration.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterFloat.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterInstant.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterInt.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterLong.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterRustBuffer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterShort.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterString.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterUByte.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterUInt.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterULong.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/FfiConverterUShort.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/ForeignBytes.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/ForeignCallback.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/InternalException.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/Pointer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/RustBuffer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/RustCallStatus.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/commonMain/kotlin/rustCall.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/callback_interface.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/compounds.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/custom.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/enum_.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/error.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/external.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/miscellany.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/mod.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/object.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/primitives.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/gen_kotlin_multiplatform/record.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/jvmMain/kotlin/ForeignBytes.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/jvmMain/kotlin/ForeignCallbackJvm.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/jvmMain/kotlin/Pointer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/jvmMain/kotlin/RustCallStatusJvm.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/lib.rs create mode 100644 anoncred-kmm/uniffi-kmm/src/nativeInterop/cinterop/uniffi.def create mode 100644 anoncred-kmm/uniffi-kmm/src/nativeInterop/cinterop/uniffi/uniffi.h create mode 100644 anoncred-kmm/uniffi-kmm/src/nativeMain/kotlin/ForeignCallback.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/nativeMain/kotlin/Pointer.kt create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/android/CallbackInterfaceTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/android/RustBufferTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/android/UniFFILibTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/CallbackInterfaceTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/CustomTypeTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/EnumTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/ErrorTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/MapTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/ObjectTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/OptionalTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/RecordTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/SequenceTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/TopLevelFunctionsTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/common/UniFFILibTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/headers/BridgingHeaderTemplate.h.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/helpers.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/jvm/CallbackInterfaceTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/jvm/RustBufferTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/jvm/UniFFILibTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/native/CallbackInterfaceTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/native/ForeignBytesTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/native/RustBufferTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/native/RustCallStatusTemplate.kt.j2 create mode 100644 anoncred-kmm/uniffi-kmm/src/templates/native/UniFFILibTemplate.kt.j2 diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml new file mode 100644 index 00000000..e1d9d1b8 --- /dev/null +++ b/.github/workflows/lint-pr.yml @@ -0,0 +1,16 @@ +--- +name: 'Pull Request - Linter' + +env: + ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} + +on: [pull_request] + +jobs: + lint_pr: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 00000000..a36bb2b7 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,84 @@ +--- +name: 'Pull Request - Compile Build' + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.head_ref }}${{ github.ref }} + cancel-in-progress: true + +env: + JAVA_VERSION: 11 + ATALA_GITHUB_ACTOR: ${{ secrets.ATALA_GITHUB_ACTOR }} + ATALA_GITHUB_TOKEN: ${{ secrets.ATALA_GITHUB_TOKEN }} + +on: [pull_request] + +jobs: + build: + runs-on: macos-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.ATALA_GITHUB_TOKEN }} + fetch-depth: 0 + + - name: "Install Java ${{ env.JAVA_VERSION }}" + uses: actions/setup-java@v3 + with: + java-version: "${{ env.JAVA_VERSION }}" + distribution: zulu + + - name: Gradle Build Action + uses: gradle/gradle-build-action@v2 + + - name: Test Kotlin code is properly formatted + working-directory: ./anoncred-kmm + run: ./gradlew ktlintCheck + + - name: Install Homebrew + run: > + /bin/bash -c "$(curl -fsSL + https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + - name: "Install autoconf, automake, libtool" + run: | + brew install autoconf automake libtool + + - name: "Install Mac ToolChain" + run: | + brew tap messense/macos-cross-toolchains + + - name: "Brew Linking issue fix" + run: | + rm '/usr/local/bin/2to3' + + - name: "Install Linux GNU for x86_64" + run: | + rm '/usr/local/bin/2to3-3.11' + rm '/usr/local/bin/idle3' + brew install --overwrite x86_64-unknown-linux-gnu + + - name: "Install Linux GNU for aarch64" + run: | + brew install --overwrite aarch64-unknown-linux-gnu + + - name: "Install Rust" + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -y + + - name: "Install Rust Targets" + run: | + rustup target add armv7-linux-androideabi & rustup target add i686-linux-android & rustup target add aarch64-linux-android & rustup target add x86_64-linux-android & rustup target add aarch64-apple-darwin & rustup target add x86_64-apple-darwin & rustup target add aarch64-unknown-linux-gnu & rustup target add x86_64-unknown-linux-gnu + + - name: "Install Rust Cargo NDK" + run: | + cargo install cargo-ndk + + - name: Build Check All tests + working-directory: ./anoncred-kmm + run: ./gradlew :anoncreds-kmp:allTests diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2e71c1f..eae6047e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,8 +63,64 @@ jobs: file: uniffi/output-frameworks/anoncreds-swift/libanoncreds.xcframework.zip asset_name: "libanoncreds.xcframework.zip" + + build-kmp: + name: "Build KMP" + runs-on: macos-latest + steps: + - name: Checkout the repo + uses: actions/checkout@v3 + with: + submodules: recursive + token: ${{ secrets.ATALA_GITHUB_TOKEN }} + fetch-depth: 0 + + - name: "Install Java ${{ env.JAVA_VERSION }}" + uses: actions/setup-java@v3 + with: + java-version: "${{ env.JAVA_VERSION }}" + distribution: zulu + + - name: Install Homebrew + run: > + /bin/bash -c "$(curl -fsSL + https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + + - name: "Install autoconf, automake, libtool" + run: | + brew install autoconf automake libtool + + - name: "Install Mac ToolChain" + run: | + brew tap messense/macos-cross-toolchains + + - name: "Install Linux GNU for x86_64" + run: | + brew install x86_64-unknown-linux-gnu + + - name: "Install Linux GNU for aarch64" + run: | + brew install aarch64-unknown-linux-gnu + + - name: "Install Rust" + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + - name: "Install Rust Targets" + run: | + rustup target add armv7-linux-androideabi & rustup target add i686-linux-android & rustup target add aarch64-linux-android & rustup target add x86_64-linux-android & rustup target add aarch64-apple-darwin & rustup target add x86_64-apple-darwin & rustup target add aarch64-unknown-linux-gnu & rustup target add x86_64-unknown-linux-gnu + + - name: "Install Rust Cargo NDK" + run: | + cargo install cargo-ndk + + - name: "Publish to GitHub Maven" + working-directory: ./anoncred-kmm + run: | + ./gradlew :anoncreds-kmp:publishAllPublicationsToGitHubPackagesRepository + build-release: - needs: build-swift-package + needs: [build-swift-package, build-kmp] name: Build Library strategy: @@ -153,4 +209,3 @@ jobs: tag: ${{github.event.inputs.tag}} file: library-${{ matrix.architecture }}.tar.gz asset_name: "library-${{ matrix.architecture }}-${{ github.sha }}.tar.gz" - diff --git a/.gitignore b/.gitignore index 9009006d..3393ddea 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,15 @@ Cargo.lock uniffi/targets uniffi/wrappers libanoncreds.xcframework.zip -.swiftpm \ No newline at end of file +.swiftpm + +.gradle +.idea +.DS_STORE +anoncred-wrapper-rust/build/generated +**/build/* +uniffi-kmm/Cargo.lock +uniffi-kmm/target +local.properties +anoncred-kmm/anoncred-wrapper-rust/src/* +**/.DS_Store diff --git a/anoncred-kmm/.editorconfig b/anoncred-kmm/.editorconfig new file mode 100644 index 00000000..d0bbae6a --- /dev/null +++ b/anoncred-kmm/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*.{kt,kts}] +ktlint_code_style = ktlint_official +ktlint_standard_no_semi = disabled +ktlint_standard_trailing-comma-on-call-site = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_standard_function-signature = disabled +ktlint_standard_max-line-length = disabled +ktlint_standard_property-naming = disabled diff --git a/anoncred-kmm/.gitignore b/anoncred-kmm/.gitignore new file mode 100644 index 00000000..72301a27 --- /dev/null +++ b/anoncred-kmm/.gitignore @@ -0,0 +1,8 @@ +.gradle +.idea +.DS_STORE +anoncred-wrapper-rust/build/generated +**/build/* +uniffi-kmm/Cargo.lock +uniffi-kmm/target +local.properties \ No newline at end of file diff --git a/anoncred-kmm/.mega-linter.yml b/anoncred-kmm/.mega-linter.yml new file mode 100644 index 00000000..2ebf3bfc --- /dev/null +++ b/anoncred-kmm/.mega-linter.yml @@ -0,0 +1,28 @@ +--- +APPLY_FIXES: none +FILTER_REGEX_EXCLUDE: (karma.config.js|polyfill.js|timeout.js) +VALIDATE_ALL_CODEBASE: true +REPOSITORY_DEVSKIM_DISABLE_ERRORS: true +MARKDOWN_MARKDOWN_LINK_CHECK_FILTER_REGEX_EXCLUDE: (pull-request.yml|Deployment.yml|badge.svg) + +DISABLE: + - COPYPASTE + - SPELL + +DISABLE_LINTERS: + - MARKDOWN_MARKDOWN_LINK_CHECK + - C_CPPLINT + - CPP_CPPLINT + - BASH_SHELLCHECK + - BASH_EXEC + - REPOSITORY_TRIVY + - REPOSITORY_TRUFFLEHOG + - REPOSITORY_KICS + - REPOSITORY_CHECKOV + - RUST_CLIPPY + +DISABLE_ERRORS_LINTERS: + - REPOSITORY_TRUFFLEHOG + - REPOSITORY_TRIVY + - REPOSITORY_KICS + - RUST_CLIPPY diff --git a/anoncred-kmm/README.md b/anoncred-kmm/README.md new file mode 100644 index 00000000..f0d903f3 --- /dev/null +++ b/anoncred-kmm/README.md @@ -0,0 +1,53 @@ +# AnonCred-KMP + +[![Kotlin](https://img.shields.io/badge/kotlin-1.8.20-blue.svg?logo=kotlin)](http://kotlinlang.org) + +![apple-silicon](https://camo.githubusercontent.com/a92c841ffd377756a144d5723ff04ecec886953d40ac03baa738590514714921/687474703a2f2f696d672e736869656c64732e696f2f62616467652f737570706f72742d2535424170706c6553696c69636f6e2535442d3433424246462e7376673f7374796c653d666c6174) +![ios](https://camo.githubusercontent.com/1fec6f0d044c5e1d73656bfceed9a78fd4121b17e82a2705d2a47f6fd1f0e3e5/687474703a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d696f732d4344434443442e7376673f7374796c653d666c6174) +![jvm](https://camo.githubusercontent.com/700f5dcd442fd835875568c038ae5cd53518c80ae5a0cf12c7c5cf4743b5225b/687474703a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6a766d2d4442343133442e7376673f7374796c653d666c6174) +![macos](https://camo.githubusercontent.com/1b8313498db244646b38a4480186ae2b25464e5e8d71a1920c52b2be5212b909/687474703a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61636f732d3131313131312e7376673f7374796c653d666c6174) + +## Introduction + +This is a Kotlin MultiPlatform (KMP) wrapper of a rust library and reference implementation of the [Anoncreds V1.0 +specification](https://hyperledger.github.io/anoncreds-spec/). + + +The AnonCreds (Anonymous Credentials) specification is based on the open source verifiable credential implementation of AnonCreds that has been in use since 2017, initially as part of the Hyperledger Indy open source project and now in the Hyperledger AnonCreds project. The extensive use of AnonCreds around the world has made it a de facto standard for ZKP-based verifiable credentials, and this specification is the formalization of that implementation. + +## Library + +AnonCred-KMP exposes three main parts: [`issuer`](./anoncred-wrapper-rust/src/issuer/mod.rs), +[`prover`](./anoncred-wrapper-rust/src/prover/mod.rs) and +[`verifier`](./anoncred-wrapper-rust/src/verifier/mod.rs). + +The library provides wrapper for the following operations + +### Issuer + +- Create a [schema](https://hyperledger.github.io/anoncreds-spec/#schema-publisher-publish-schema-object) +- Create a [credential definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-credential-definition-object) +- Create a [revocation registry definition](https://hyperledger.github.io/anoncreds-spec/#issuer-create-and-publish-revocation-registry-objects) +- Create a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object) +- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object) +- Update a [revocation status list](https://hyperledger.github.io/anoncreds-spec/#publishing-the-initial-initial-revocation-status-list-object)'s timestamp +- Create a [credential offer](https://hyperledger.github.io/anoncreds-spec/#credential-offer) +- Create a [credential](https://hyperledger.github.io/anoncreds-spec/#issue-credential) + +### Prover / Holder + +- Create a [credential request](https://hyperledger.github.io/anoncreds-spec/#credential-request) +- Process an incoming [credential](https://hyperledger.github.io/anoncreds-spec/#receiving-a-credential) +- Create a [presentation](https://hyperledger.github.io/anoncreds-spec/#generate-presentation) +- Create, and update, a revocation state +- Create, and update, a revocation state with a witness + +### Verifier + +- [Verify a presentation](https://hyperledger.github.io/anoncreds-spec/#verify-presentation) +- generate a nonce + +## Requirement + +- Rust v1.72.0 +- KMP v1.8.20 diff --git a/anoncred-kmm/anoncred-wrapper-rust/.cargo/config.toml b/anoncred-kmm/anoncred-wrapper-rust/.cargo/config.toml new file mode 100644 index 00000000..d228cdcb --- /dev/null +++ b/anoncred-kmm/anoncred-wrapper-rust/.cargo/config.toml @@ -0,0 +1,12 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" +[target.x86_64-unknown-linux-gnu] +linker = "x86_64-linux-gnu-gcc" +[target.aarch64-linux-android] +linker = "aarch64-linux-android21-clang++" +[target.x86_64-linux-android] +linker = "x86_64-linux-android21-clang++" +[target.i686-linux-android] +linker = "i686-linux-android21-clang++" +[target.armv7-linux-androideabi] +linker = "armv7a-linux-androideabi21-clang++" diff --git a/anoncred-kmm/anoncred-wrapper-rust/.gitignore b/anoncred-kmm/anoncred-wrapper-rust/.gitignore new file mode 100644 index 00000000..33491a0e --- /dev/null +++ b/anoncred-kmm/anoncred-wrapper-rust/.gitignore @@ -0,0 +1,15 @@ +# Generated by Cargo +# will have compiled files and executables +.DS_Store +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/anoncred-kmm/anoncred-wrapper-rust/Cargo.toml b/anoncred-kmm/anoncred-wrapper-rust/Cargo.toml new file mode 100644 index 00000000..556721d6 --- /dev/null +++ b/anoncred-kmm/anoncred-wrapper-rust/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "anoncreds_wrapper" +version = "0.1.0" +authors = ['Ahmed Moussa '] +edition = "2021" +description = 'FFI wrapper for Anoncreds' + +[lib] +name = "anoncreds_wrapper" +path = "src/lib.rs" +crate-type = ['cdylib', 'staticlib'] + +[dependencies.anoncreds_core] +path = '../' +package = "anoncreds" +features = ["vendored"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +uniffi = "0.23.0" +uniffi_macros = "0.23.0" +once_cell = "1.12" +thiserror = "1.0" +lazy_static = "1.3" +futures = { version = "0.3.17", features = ["thread-pool"] } +num_cpus = "1.8.0" +async-trait = '0.1' +serde = { version = "1.0", features = ["derive"] } +serde_json = '1.0' +ursa = { version = "0.3.7", default-features = false, features = ["cl_native", "serde"] } +swift-bridge = "0.1.51" +openssl = "0.10.45" +amcl = "0.2" + +[dev-dependencies.tokio] +version = '1.9' +features = ['rt', 'macros'] + +[build-dependencies] +uniffi = { version = "0.23.0", features=["build"] } +uniffi_bindgen = "0.23.0" +camino = "1.1.1" +swift-bridge-build = "0.1.51" +uniffi-kmm = { path = "../uniffi-kmm" } diff --git a/anoncred-kmm/anoncred-wrapper-rust/build.gradle.kts b/anoncred-kmm/anoncred-wrapper-rust/build.gradle.kts new file mode 100644 index 00000000..35320ea1 --- /dev/null +++ b/anoncred-kmm/anoncred-wrapper-rust/build.gradle.kts @@ -0,0 +1,640 @@ +import org.gradle.internal.os.OperatingSystem +import java.io.ByteArrayOutputStream + +val os: OperatingSystem = OperatingSystem.current() +val generatedDir = buildDir.resolve("generated") +val crateDir = projectDir +val crateTargetDir = projectDir.resolve("target") +val crateTargetLibDir = crateTargetDir.resolve("debug") +val pathToCopyBindingInto = rootDir.resolve("anoncreds-kmp").resolve("build").resolve("generated") + +@Throws(GradleException::class) +fun getNDKOSVariant(): String { + return if (os.isMacOsX) { + "darwin-x86_64" + } else if (os.isLinux) { + "linux-x86_64" + } else { + // It would be windows-x86_64 but we don't support Windows enviroment + throw GradleException("Unsported OS: ${os.name}") + } +} + +val minAndroidVersion: Int = 21 +val ANDROID_SDK = System.getenv("ANDROID_HOME") +val NDK = System.getenv("ANDROID_NDK") +val TOOLCHAIN = "$NDK/toolchains/llvm/prebuilt/${getNDKOSVariant()}" +val AR = "$TOOLCHAIN/bin/llvm-ar" +val CC = "$TOOLCHAIN/bin/aarch64-linux-android$minAndroidVersion-clang" +val CXX = "$TOOLCHAIN/bin/aarch64-linux-android$minAndroidVersion-clang++" +val LD = "$TOOLCHAIN/bin/ld" +val RANLIB = "$TOOLCHAIN/bin/llvm-ranlib" +val STRIP = "$TOOLCHAIN/bin/llvm-strip" + +/** + * Run CommandLine and return the output + * @param spec executions commands to run + * @return the result of these executions commands like they would show up in system terminal + */ +fun Project.execWithOutput(spec: ExecSpec.() -> Unit): String { + return ByteArrayOutputStream().use { outputStream -> + exec { + this.spec() + this.standardOutput = outputStream + } + outputStream.toString().trim() + } +} + +/* +fun Project.commandLineWithOutput(vararg commands: String) { + val pb = ProcessBuilder() +} +*/ + +// TODO: Replace commandLine with +// val pb = ProcessBuilder("brew", "--version") +// pb.start().waitFor() +// println(pb.output) + +/** + * Delete the generated `Target` folder that is being generated by Rust Cargo + */ +val rustClean by tasks.register("rustClean") { + group = "rust" + description = "Delete the generated files and folders that is being generated by this Gradle Script" + delete(projectDir.resolve("target")) + delete(rootDir.resolve("target")) + delete(buildDir) +} + +tasks.matching { it.name == "clean" }.all { + dependsOn(rustClean) +} + +/** + * Building the original Rust anoncreds lib + * @note no longer needed. + */ +val buildAnoncredsLib by tasks.register("buildAnoncredsLib") { + group = "rust" + description = "Building the original Rust anoncreds lib" + onlyIf { + rootDir + .resolve("target") + .resolve("release") + .exists() + .not() + } + workingDir = rootDir + inputs.files(rootDir.resolve("Cargo.toml")) + outputs.files(fileTree(rootDir.resolve("target"))) + commandLine("cargo", "build", "--release") +} + +// Compiling Tasks + +val buildAnonCredWrapperForMacOSArch64 by tasks.register("buildAnonCredWrapperForMacOSArch64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for macOS aarch64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("aarch64-apple-darwin"))) + commandLine("cargo", "build", "--release", "--target", "aarch64-apple-darwin", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForMacOSX8664 by tasks.register("buildAnonCredWrapperForMacOSX86_64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for macOS x86_64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("x86_64-apple-darwin"))) + commandLine("cargo", "build", "--release", "--target", "x86_64-apple-darwin", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForMacOSUniversal by tasks.register("buildAnonCredWrapperForMacOSUniversal") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for macOS" + dependsOn(buildAnonCredWrapperForMacOSArch64, buildAnonCredWrapperForMacOSX8664) +} + +val buildAnonCredWrapperForiOSArch64 by tasks.register("buildAnonCredWrapperForiOSArch64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for iOS aarch64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("aarch64-apple-ios"))) + commandLine("cargo", "build", "--release", "--target", "aarch64-apple-ios", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForiOSArch64Sim by tasks.register("buildAnonCredWrapperForiOSArch64Sim") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for iOS aarch64 sim" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("aarch64-apple-ios-sim"))) + commandLine("cargo", "build", "--release", "--target", "aarch64-apple-ios-sim", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForiOSX8664 by tasks.register("buildAnonCredWrapperForiOSX86_64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for iOS x86_64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("x86_64-apple-ios"))) + commandLine("cargo", "build", "--release", "--target", "x86_64-apple-ios", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForiOSUniversal by tasks.register("buildAnonCredWrapperForiOSUniversal") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for iOS" + dependsOn(buildAnonCredWrapperForiOSArch64, buildAnonCredWrapperForiOSArch64Sim, buildAnonCredWrapperForiOSX8664) +} + +val buildAnonCredWrapperForLinuxX8664 by tasks.register("buildAnonCredWrapperForLinuxX86_64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Linux x86_64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("x86_64-unknown-linux-gnu"))) + commandLine("cargo", "build", "--release", "--target", "x86_64-unknown-linux-gnu", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForLinuxArch64 by tasks.register("buildAnonCredWrapperForLinuxArch64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Linux aarch64" + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("aarch64-unknown-linux-gnu"))) + commandLine("cargo", "build", "--release", "--target", "aarch64-unknown-linux-gnu", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForLinuxUniversal by tasks.register("buildAnonCredWrapperForLinuxUniversal") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Linux" + dependsOn(buildAnonCredWrapperForLinuxArch64, buildAnonCredWrapperForLinuxX8664) +} + +val buildAnonCredWrapperForAndroidX8664 by tasks.register("buildAnonCredWrapperForAndroidX86_64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Android X86_64" + val localEnv = this.environment + localEnv += mapOf( + "PATH" to "${environment["PATH"]}:$TOOLCHAIN:$AR:$CC:$CXX:$LD:$RANLIB:$STRIP", + "ANDROID_SDK" to ANDROID_SDK, + "NDK" to NDK, + "TOOLCHAIN" to TOOLCHAIN, + "AR" to AR, + "CC" to CC, + "CXX" to CXX, + "LD" to LD, + "RANLIB" to RANLIB, + "STRIP" to STRIP + ) + this.environment = localEnv + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("x86_64-linux-android"))) + commandLine("cargo", "ndk", "build", "--release", "--target", "x86_64-linux-android", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForAndroidArch64 by tasks.register("buildAnonCredWrapperForAndroidArch64") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Android arch64" + val localEnv = this.environment + localEnv += mapOf( + "PATH" to "${environment["PATH"]}:$TOOLCHAIN:$AR:$CC:$CXX:$LD:$RANLIB:$STRIP", + "ANDROID_SDK" to ANDROID_SDK, + "NDK" to NDK, + "TOOLCHAIN" to TOOLCHAIN, + "AR" to AR, + "CC" to CC, + "CXX" to CXX, + "LD" to LD, + "RANLIB" to RANLIB, + "STRIP" to STRIP + ) + this.environment = localEnv + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("aarch64-linux-android"))) + commandLine("cargo", "ndk", "build", "--release", "--target", "aarch64-linux-android", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForAndroidI686 by tasks.register("buildAnonCredWrapperForAndroidI686") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Android I686" + val localEnv = this.environment + localEnv += mapOf( + "PATH" to "${environment["PATH"]}:$TOOLCHAIN:$AR:$CC:$CXX:$LD:$RANLIB:$STRIP", + "ANDROID_SDK" to ANDROID_SDK, + "NDK" to NDK, + "TOOLCHAIN" to TOOLCHAIN, + "AR" to AR, + "CC" to CC, + "CXX" to CXX, + "LD" to LD, + "RANLIB" to RANLIB, + "STRIP" to STRIP + ) + this.environment = localEnv + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("i686-linux-android"))) + commandLine("cargo", "ndk", "build", "--release", "--target", "i686-linux-android", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForAndroidArmv7a by tasks.register("buildAnonCredWrapperForAndroidArmv7a") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Android Armv7a" + val localEnv = this.environment + localEnv += mapOf( + "PATH" to "${environment["PATH"]}:$TOOLCHAIN:$AR:$CC:$CXX:$LD:$RANLIB:$STRIP", + "ANDROID_SDK" to ANDROID_SDK, + "NDK" to NDK, + "TOOLCHAIN" to TOOLCHAIN, + "AR" to AR, + "CC" to CC, + "CXX" to CXX, + "LD" to LD, + "RANLIB" to RANLIB, + "STRIP" to STRIP + ) + this.environment = localEnv + inputs.files(fileTree(projectDir.resolve("src"))) + outputs.files(fileTree(projectDir.resolve("target").resolve("armv7-linux-androideabi"))) + commandLine("cargo", "ndk", "build", "--release", "--target", "armv7-linux-androideabi", "--target-dir", "${projectDir.resolve("target")}") +} + +val buildAnonCredWrapperForAndroidUniversal by tasks.register("buildAnonCredWrapperForAndroidUniversal") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper for Android" + dependsOn(buildAnonCredWrapperForAndroidX8664, buildAnonCredWrapperForAndroidArch64, buildAnonCredWrapperForAndroidI686, buildAnonCredWrapperForAndroidArmv7a) +} + +val buildAnonCredWrapper by tasks.register("buildAnonCredWrapper") { + group = "rust-compiling" + description = "Build and compile AnonCred Wrapper" + dependsOn(buildAnonCredWrapperForMacOSUniversal, buildAnonCredWrapperForLinuxUniversal, buildAnonCredWrapperForAndroidUniversal) // buildAnonCredWrapperForiOSUniversal +} + +// Copy Bindings Tasks + +val copyGeneratedBinaryForMacOSX8664 by tasks.register("copyGeneratedBinaryForMacOSX86_64") { + group = "rust-compiling" + description = "Copy all generated macOS x86_64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("x86_64-apple-darwin").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("jvm").resolve("main").resolve("darwin-x86-64")) + dependsOn(buildAnonCredWrapperForMacOSX8664) +} + +val copyGeneratedBinaryForMacOSArch64 by tasks.register("copyGeneratedBinaryForMacOSArch64") { + group = "rust-compiling" + description = "Copy all generated macOS aarch64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("aarch64-apple-darwin").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("jvm").resolve("main").resolve("darwin-aarch64")) + dependsOn(buildAnonCredWrapperForMacOSArch64) +} + +val copyGeneratedBinaryForMacOS by tasks.register("copyGeneratedBinaryForMacOS") { + group = "rust-compiling" + description = "Copy all generated macOS binaries to generated resources folder" + dependsOn(copyGeneratedBinaryForMacOSX8664, copyGeneratedBinaryForMacOSArch64) +} + +val copyGeneratedBinaryForLinuxX8664 by tasks.register("copyGeneratedBinaryForLinuxX86_64") { + group = "rust-compiling" + description = "Copy all generated Linux x86_64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("x86_64-unknown-linux-gnu").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("jvm").resolve("main").resolve("linux-x86-64")) + dependsOn(buildAnonCredWrapperForLinuxX8664) +} + +val copyGeneratedBinaryForLinuxArch64 by tasks.register("copyGeneratedBinaryForLinuxArch64") { + group = "rust-compiling" + description = "Copy all generated Linux aarch64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("aarch64-unknown-linux-gnu").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("jvm").resolve("main").resolve("linux-aarch64")) + dependsOn(buildAnonCredWrapperForLinuxArch64) +} + +val copyGeneratedBinaryForLinux by tasks.register("copyGeneratedBinaryForLinux") { + group = "rust-compiling" + description = "Copy all generated Linux binaries to generated resources folder" + dependsOn(copyGeneratedBinaryForLinuxX8664, copyGeneratedBinaryForLinuxArch64) +} + +val copyGeneratedBinaryForiOS by tasks.register("copyGeneratedBinaryForiOS") { + group = "rust-compiling" + description = "Copy all generated iOS binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("ios-universal").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("processedResources").resolve("binaries").resolve("ios")) + dependsOn(buildAnonCredWrapperForiOSUniversal) +} + +val copyGeneratedBinaryForAndroidX8664 by tasks.register("copyGeneratedBinaryForAndroidX86_64") { + group = "rust-compiling" + description = "Copy all generated Android X86_64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("x86_64-linux-android").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("android").resolve("main").resolve("jniLibs").resolve("x86-64")) + dependsOn(buildAnonCredWrapperForAndroidX8664) +} + +val copyGeneratedBinaryForAndroidArch64 by tasks.register("copyGeneratedBinaryForAndroidArch64") { + group = "rust-compiling" + description = "Copy all generated Android aarch64 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("aarch64-linux-android").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("android").resolve("main").resolve("jniLibs").resolve("arm64-v8a")) + dependsOn(buildAnonCredWrapperForAndroidArch64) +} + +val copyGeneratedBinaryForAndroidI686 by tasks.register("copyGeneratedBinaryForAndroidI686") { + group = "rust-compiling" + description = "Copy all generated Android i686 binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("i686-linux-android").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("android").resolve("main").resolve("jniLibs").resolve("x86")) + dependsOn(buildAnonCredWrapperForAndroidI686) +} + +val copyGeneratedBinaryForAndroidArmv7a by tasks.register("copyGeneratedBinaryForAndroidArmv7a") { + group = "rust-compiling" + description = "Copy all generated Android armv7a binaries to generated resources folder" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + include("*.so", "*.a", "*.d", "*.dylib") + from(projectDir.resolve("target").resolve("armv7-linux-androideabi").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("android").resolve("main").resolve("jniLibs").resolve("armeabi-v7a")) + dependsOn(buildAnonCredWrapperForAndroidArmv7a) +} + +val copyGeneratedBinaryForAndroid by tasks.register("copyGeneratedBinaryForAndroid") { + group = "rust-compiling" + description = "Copy all generated Android binaries to generated resources folder" + dependsOn(copyGeneratedBinaryForAndroidArch64, copyGeneratedBinaryForAndroidX8664, copyGeneratedBinaryForAndroidI686, copyGeneratedBinaryForAndroidArmv7a) +} + +val copyGeneratedBinariesToCorrectLocation by tasks.register("copyGeneratedBinariesToCorrectLocation") { + group = "rust-compiling" + description = "Copy all generated binaries to generated resources folder" + dependsOn(copyGeneratedBinaryForMacOS, copyGeneratedBinaryForLinux, copyGeneratedBinaryForAndroid) // copyGeneratedBinaryForiOS +} + +/** + * Copy generated bindings to the `anoncreds-kmm` module + */ +val copyBindings by tasks.register("copyBindings") { + group = "rust-compiling" + description = "Copy generated bindings to the `anoncreds-kmm` module" + from(buildDir.resolve("generated")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generated")) + dependsOn(copyGeneratedBinariesToCorrectLocation) +} + +/** + * Copy generated dylib to correct location + */ +val copyAnoncredsBinariesToProcessedResources by tasks.register("copyAnoncredsBinariesToProcessedResources") { + group = "rust-compiling" + description = "Copy generated AnonCreds binaries to generated resources folder" + include("*.so", "*.a", "*.d", "*.dylib") + from(rootDir.resolve("rust-libs").resolve("anoncreds-rs").resolve("target").resolve("release")) + into(rootDir.resolve("anoncred-kmm").resolve("anoncreds-kmp").resolve("build").resolve("generatedResources").resolve("jvm").resolve("main")) + dependsOn(buildAnoncredsLib) +} + +/** + * Generate rust documentation + */ +val generateDocumentation by tasks.register("rustDoc") { + group = "documentation" + description = "Generate rust documentation" + commandLine("cargo", "doc") + dependsOn(buildAnonCredWrapper) +} + +// Verification Tasks + +/** + * Verify that all used rust targets are installed + */ +val verifyCrateTargets by tasks.register("verifyCrateTargets") { + group = "install" + description = "Verify that all used rust targets are installed" + val targets = if (os.isMacOsX) { + listOf( + "armv7-linux-androideabi", + "i686-linux-android", + "aarch64-linux-android", + "x86_64-linux-android", + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu" + ) + } else if (os.isLinux) { + listOf( + "armv7-linux-androideabi", + "i686-linux-android", + "aarch64-linux-android", + "x86_64-linux-android", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu" + ) + } else { + throw GradleException("Unsupported OS ${os.name}") + } + + // Get all available targets + val availableTargetsString = execWithOutput { + commandLine("rustup", "target", "list") + } + var output = "" + for (target in targets) { + output += if (availableTargetsString.contains("$target (installed)").not()) { + // Install target if not installed + execWithOutput { + commandLine("rustup", "target", "add", target) + } + "Installing target: $target\n" + } else { + "Target $target already installed\n" + } + } + commandLine("echo", output) +} + +/** + * Verify that Rust is installed and if not, install it + */ +val verifyRustInstalled by tasks.register("verifyRustInstalled") { + group = "install" + description = "Verify that Rust is installed and if not, install it" + if (os.isLinux || os.isMacOsX) { + val output = execWithOutput { + commandLine("rustup", "--version") + } + if (output.contains("command not found")) { + // Install Rust + execWithOutput { + commandLine("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh") + } + execWithOutput { + commandLine("echo", "Installed Rust") + } + } else { + execWithOutput { + commandLine("echo", "Rust is already installed") + } + } + } else { + throw GradleException("Unsupported OS ${os.name}") + } +} + +/** + * Verify that all needed tools for Rust are installed correctly + */ +val verifyRust by tasks.register("verifyRust") { + group = "install" + description = "Verify that all needed tools for Rust are installed correctly" + doFirst { + verifyRustInstalled + } + dependsOn(verifyRustInstalled, verifyCrateTargets) +} + +// Installation Tasks + +val installCargoNDK by tasks.register("installCargoNDK") { + group = "install" + description = "Install Cargo-NDK" + commandLine("cargo", "install", "cargo-ndk") +} + +val installHomeBrew by tasks.register("installHomeBrew") { + group = "install" + description = "Install HomeBrew" + onlyIf { + os.isMacOsX + } + val output = execWithOutput { + commandLine("brew", "--version") + } + if (output.contains("command not found").not()) { + execWithOutput { + commandLine("echo", "HomeBrew already installed") + } + } else { + execWithOutput { + commandLine("/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"") + } + } +} + +val updateLinuxApt by tasks.register("updateLinuxApt") { + group = "install" + description = "Update Linux Apt" + onlyIf { + os.isLinux + } + commandLine("apt-get", "update", "-y") +} + +val installMacToolChain by tasks.register("installMacToolChain") { + group = "install" + description = "Install Mac ToolChain" + onlyIf { + os.isMacOsX + } + commandLine("brew", "tap", "messense/macos-cross-toolchains") +} + +val installx8664LinuxGNU by tasks.register("installx86_64LinuxGNU") { + group = "install" + description = "Install Linux GNU for x86_64" + if (os.isMacOsX) { + commandLine("brew", "install", "x86_64-unknown-linux-gnu") + } else if (os.isLinux) { + commandLine("apt-get", "install", "-y", "gcc-x86-64-linux-gnu") + } else { + throw GradleException("${os.name} is not supported. Need to switch to macOS or Linux") + } +} + +val installAarch64LinuxGNU by tasks.register("installAarch64LinuxGNU") { + group = "install" + description = "Install Linux GNU for aarch64" + if (os.isMacOsX) { + commandLine("brew", "install", "aarch64-unknown-linux-gnu") + } else if (os.isLinux) { + commandLine("apt-get", "install", "-y", "gcc-4.8-aarch64-linux-gnu") + } else { + throw GradleException("${os.name} is not supported. Need to switch to macOS or Linux") + } +} + +/** + * Install all required compiler tools that is needed for this project + * - Homebrew for macOS + * - MacToolChains for macOS + * - Update APT for Linux + * - x86_64-linux-gnu for macOS & Linux + * - arch64-linux-gnu for macOS & Linux + */ +val requiredInstallation by tasks.register("RequiredInstallation") { + group = "install" + description = """ + Install all required compiler tools that is needed for this project + - Homebrew for macOS + - MacToolChains for macOS + - Update APT for Linux + - x86_64-linux-gnu for macOS & Linux + - arch64-linux-gnu for macOS & Linux + """.trimIndent() + dependsOn(installHomeBrew, updateLinuxApt, installMacToolChain, installx8664LinuxGNU, installAarch64LinuxGNU, installCargoNDK) +} + +val deleteRustSrcFiles by tasks.register("deleteRustSrcFiles") { + group = "rust" + project.delete( + fileTree(rootDir.resolve("anoncred-kmm").resolve("anoncred-wrapper-rust").resolve("src")) + ) +} + +val moveRustSrcFiles by tasks.register("moveRustSrcFiles") { + group = "rust" + description = "Move rust src files from main rust folder to our sub module folder to generate all needed code" + duplicatesStrategy = DuplicatesStrategy.INCLUDE + from( + fileTree(rootDir.resolve("uniffi").resolve("src")) + ) + into(rootDir.resolve("anoncred-wrapper-rust").resolve("src")) + dependsOn(deleteRustSrcFiles) +} + +/** + * The main build Rust lib task. It will do the following: + * - Build the lib + * - Generate the bindings + * - Move the generated bindings to the lib module to be used in Kotlin KMM + */ +val buildRust by tasks.register("buildRust") { + group = "rust" + description = """ + The main build Rust lib task. It will do the following: + - Move Rust src files + - Build the lib + - Generate the bindings + - Move the generated bindings to the lib module to be used in Kotlin KMM + """.trimIndent() + doFirst { + moveRustSrcFiles + } + mustRunAfter(moveRustSrcFiles) + dependsOn(moveRustSrcFiles, requiredInstallation, verifyRust) // ,copyBindings, copyAnoncredsBinariesToProcessedResources) +} diff --git a/anoncred-kmm/anoncred-wrapper-rust/build.rs b/anoncred-kmm/anoncred-wrapper-rust/build.rs new file mode 100644 index 00000000..fe151f1e --- /dev/null +++ b/anoncred-kmm/anoncred-wrapper-rust/build.rs @@ -0,0 +1,14 @@ +use camino::Utf8Path; +use uniffi_kmm::KotlinBindingGenerator; + +// Script responsible for generating a scaffold using UniFFI +fn main() { + let out_dir = Utf8Path::new("build/generated"); + uniffi::generate_scaffolding("./src/anoncreds.udl").unwrap(); + uniffi_bindgen::generate_external_bindings( + KotlinBindingGenerator {}, + "./src/anoncreds.udl", + None::<&Utf8Path>, + Some(out_dir), + ).unwrap(); +} diff --git a/anoncred-kmm/anoncreds-kmp/build.gradle.kts b/anoncred-kmm/anoncreds-kmp/build.gradle.kts new file mode 100644 index 00000000..c9fbdb94 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/build.gradle.kts @@ -0,0 +1,295 @@ +import org.gradle.internal.os.OperatingSystem +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +val os: OperatingSystem = OperatingSystem.current() + +plugins { + kotlin("multiplatform") + id("com.android.library") + id("maven-publish") +} + +apply(plugin = "kotlinx-atomicfu") +version = "1.1.0" +group = "io.iohk.atala.prism.anoncredskmp" + +fun KotlinNativeCompilation.anoncredsCinterops(type: String) { + cinterops { + val anoncreds_wrapper by creating { + val crate = this.name + packageName("$crate.cinterop") + header( + buildDir + .resolve("generated") + .resolve("nativeInterop") + .resolve("cinterop") + .resolve("headers") + .resolve(crate) + .resolve("$crate.h") + ) + tasks.named(interopProcessingTaskName) { + dependsOn(":anoncred-wrapper-rust:buildRust") + } + when (type) { + "macosX64" -> { + extraOpts( + "-libraryPath", + rootDir + .resolve("anoncred-wrapper-rust") + .resolve("target") + .resolve("x86_64-apple-darwin") + .resolve("release") + .absolutePath + ) + } + "macosArm64" -> { + extraOpts( + "-libraryPath", + rootDir + .resolve("anoncred-wrapper-rust") + .resolve("target") + .resolve("aarch64-apple-darwin") + .resolve("release") + .absolutePath + ) + } +// "ios" -> { +// extraOpts( +// "-libraryPath", +// rootDir +// .resolve("anoncred-wrapper-rust") +// .resolve("target") +// .resolve("ios-universal") +// .resolve("release") +// .absolutePath +// ) +// } + else -> { + throw GradleException("Unsupported linking") + } + } + } + } +} + +kotlin { + jvm { + compilations.all { + kotlinOptions.jvmTarget = "11" + } + testRuns["test"].executionTask.configure { + useJUnitPlatform() + } + } + android { + publishAllLibraryVariants() + } + +// if (os.isMacOsX) { +// macosX64("native") { +// compilations.getByName("main") { +// this.anoncredsCinterops("macosX64") +// } +// } +// if (System.getProperty("os.arch") != "x86_64") { +// macosArm64 { +// compilations.getByName("main") { +// this.anoncredsCinterops("macosArm64") +// } +// } +// } +// } + +/* + val crateTargetLibDir = rootDir.resolve("anoncred-wrapper-rust").resolve("target").resolve("debug") + val hostOs = System.getProperty("os.name") + val isMingwX64 = hostOs.startsWith("Windows") + val nativeTarget = when { + hostOs == "Mac OS X" -> { + if (System.getProperty("os.arch") != "x86_64") { + macosArm64("native") + } else { + macosX64("native") + } + } + hostOs == "Linux" -> linuxArm64("native") + isMingwX64 -> mingwX64("native") + else -> throw GradleException("Host OS is not supported in Kotlin/Native.") + } + nativeTarget.apply { + compilations.getByName("main") { + println("nativeTarget-only-main") + cinterops { + val anoncreds_wrapper by creating { + val crate = this.name + packageName("$crate.cinterop") + header( + generatedDir.resolve("nativeInterop").resolve("cinterop").resolve("headers") + .resolve(crate).resolve("$crate.h") + ) + tasks.named(interopProcessingTaskName) { + dependsOn(":anoncred-wrapper-rust:buildRust") + } + extraOpts("-libraryPath", crateTargetLibDir.absolutePath) + } + } + } + } +*/ + + sourceSets { + val commonMain by getting { + val generatedDir = buildDir + .resolve("generated") + .resolve("commonMain") + .resolve("kotlin") + kotlin.srcDir(generatedDir) + dependencies { + implementation("com.squareup.okio:okio:3.4.0") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.1") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") + } + } + val jvmMain by getting { + val generatedDir = buildDir + .resolve("generated") + .resolve("jvmMain") + .resolve("kotlin") + kotlin.srcDir(generatedDir) + val generatedResources = buildDir + .resolve("generatedResources") + .resolve("jvm") + .resolve("main") + resources.srcDir(generatedResources) + dependencies { + implementation("net.java.dev.jna:jna:5.13.0") + } + } + val jvmTest by getting + val androidMain by getting { + val generatedDir = buildDir + .resolve("generated") + .resolve("androidMain") + .resolve("kotlin") + kotlin.srcDir(generatedDir) + val generatedResources = buildDir + .resolve("generatedResources") + .resolve("android") + .resolve("main") + .resolve("jniLibs") + resources.srcDir(generatedResources) + dependencies { + implementation("net.java.dev.jna:jna:5.13.0@aar") + } + } + val androidUnitTest by getting { + dependencies { + implementation("junit:junit:4.13.2") + } + } +// if (os.isMacOsX) { +// val nativeMain by getting { // aka "macosX64" +// val generatedDir = buildDir +// .resolve("generated") +// .resolve("nativeMain") +// .resolve("kotlin") +// kotlin.srcDir(generatedDir) +// } +// val nativeTest by getting +// if (System.getProperty("os.arch") != "x86_64") { +// val macosArm64Main by getting { +// dependsOn(nativeMain) +// } +// } +// } + all { + languageSettings { + optIn("kotlin.RequiresOptIn") + optIn("kotlinx.cinterop.ExperimentalForeignApi") + } + } + } +} + +/** + * Delete the generated `Target` folder that is being generated by Rust Cargo + */ +val rustClean by tasks.register("rustClean") { + group = "rust" + delete(projectDir.resolve("target")) + dependsOn("clean") +} + +publishing { + repositories { + maven { + this.name = "GitHubPackages" + this.url = uri("https://maven.pkg.github.com/input-output-hk/anoncreds-rs/") + credentials { + this.username = System.getenv("ATALA_GITHUB_ACTOR") + this.password = System.getenv("ATALA_GITHUB_TOKEN") + } + } + } +} + +android { + ndkVersion = "26.0.10792818" + compileSdk = 33 + namespace = "io.iohk.atala.prism.anoncredskmp" + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + + sourceSets["main"].jniLibs { + setSrcDirs( + listOf( + buildDir + .resolve("generatedResources") + .resolve("android") + .resolve("main") + .resolve("jniLibs") + ) + ) + } + defaultConfig { + minSdk = 21 + targetSdk = 32 + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + /** + * Because Software Components will not be created automatically for Maven publishing from + * Android Gradle Plugin 8.0. To opt-in to the future behavior, set the Gradle property android. + * disableAutomaticComponentCreation=true in the `gradle.properties` file or use the new + * publishing DSL. + */ + publishing { + multipleVariants { + withSourcesJar() + withJavadocJar() + allVariants() + } + } +} + +afterEvaluate { + tasks.withType { + dependsOn(":anoncred-wrapper-rust:buildRust") + } + tasks.withType { + dependsOn(":anoncred-wrapper-rust:buildRust") + } + tasks.named("lintAnalyzeDebug") { + this.enabled = false + } + tasks.named("lintAnalyzeRelease") { + this.enabled = false + } +} diff --git a/anoncred-kmm/anoncreds-kmp/src/androidMain/AndroidManifest.xml b/anoncred-kmm/anoncreds-kmp/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..b662225a --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/anoncred-kmm/anoncreds-kmp/src/androidUnitTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt b/anoncred-kmm/anoncreds-kmp/src/androidUnitTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt new file mode 100644 index 00000000..ba610ec1 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/androidUnitTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt @@ -0,0 +1,8 @@ +package io.iohk.atala.prism.anoncred + +import org.junit.Ignore + +/** + * Ignore Unit tests on Android Platform + */ +actual typealias AndroidIgnore = Ignore diff --git a/anoncred-kmm/anoncreds-kmp/src/commonMain/kotlin/io/iohk/atala/prism/anoncred/Platform.kt b/anoncred-kmm/anoncreds-kmp/src/commonMain/kotlin/io/iohk/atala/prism/anoncred/Platform.kt new file mode 100644 index 00000000..d6c89392 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/commonMain/kotlin/io/iohk/atala/prism/anoncred/Platform.kt @@ -0,0 +1,10 @@ +package io.iohk.atala.prism.anoncred + +enum class Platform { + Linux, + Macos, + Windows, + Ios, + Android, + Js +} diff --git a/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt new file mode 100644 index 00000000..a7ec7389 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt @@ -0,0 +1,6 @@ +package io.iohk.atala.prism.anoncred + +/** + * Ignore Unit tests on Android Platform + */ +expect annotation class AndroidIgnore() diff --git a/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismIssuerTests.kt b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismIssuerTests.kt new file mode 100644 index 00000000..b83eb041 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismIssuerTests.kt @@ -0,0 +1,226 @@ +package io.iohk.atala.prism.anoncred + +import anoncreds_wrapper.AttributeValues +import anoncreds_wrapper.CredentialDefinitionConfig +import anoncreds_wrapper.Issuer +import anoncreds_wrapper.Nonce +import anoncreds_wrapper.Prover +import anoncreds_wrapper.RegistryType +import anoncreds_wrapper.Schema +import anoncreds_wrapper.SignatureType +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +@AndroidIgnore +class PrismIssuerTests { + @Test + fun test_PrismIssuer_createSchema() { + val expectedSchema = Schema("Moussa", "1.0", listOf("name", "age"), "sample:uri") + val scheme: Schema = Issuer().createSchema("Moussa", "1.0", "sample:uri", listOf("name", "age")) + assertEquals(expectedSchema, scheme, "scheme not equal") + assertEquals(expectedSchema.name, scheme.name, "name not correct") + assertEquals(expectedSchema.version, scheme.version, "version not correct") + assertEquals(expectedSchema.issuerId, scheme.issuerId, "issuerId not correct") + assertEquals(expectedSchema.attrNames.size, scheme.attrNames.size, "attrNames size is not correct") + expectedSchema.attrNames.forEach { + assertTrue(scheme.attrNames.contains(it)) + } + } + + @Test + fun test_PrismIssuer_createCredentialDefinition() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema: Schema = prismIssuer.createSchema("Moussa", "1.0", "sample:uri", attributeNames) + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + println(cred.credentialDefinition.getJson()) + println(cred.credentialDefinitionPrivate.getJson()) + println(cred.credentialKeyCorrectnessProof.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createRevocationRegistryDef() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + println("regDef IssuerId: ${rev.regDef.getIssuerId()}") + println("regDef CredDefId: ${rev.regDef.getCredDefId()}") + println("regDefPrivate: ${rev.regDefPrivate.getJson()}") + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createRevocationStatusList() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + println(revStatusList.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_updateRevocationStatusListTimestampOnly() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + val updatedRevStatusList = prismIssuer.updateRevocationStatusListTimestampOnly(1000u, revStatusList) + println(updatedRevStatusList.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_updateRevocationStatusList() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + val updatedRevStatusList = prismIssuer.updateRevocationStatusList(null, listOf(1u), null, rev.regDef, revStatusList) + println(updatedRevStatusList.getJson()) + assertTrue(true) + Nonce().toString() + Nonce() + } + + @Test + fun test_PrismIssuer_createCredentialOffer() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer("did:web:xyz/resource/schema", "did:web:xyz/resource/cred-def", cred.credentialKeyCorrectnessProof) + println(credentialOffer.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createCredential() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer("did:web:xyz/resource/schema", "did:web:xyz/resource/cred-def", cred.credentialKeyCorrectnessProof) + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + val credentialRequest = prismProver.createCredentialRequest("entropy", null, cred.credentialDefinition, linkSecret, "my-secret-id", credentialOffer) + val credentialValues = listOf(AttributeValues("name", "Moussa")) + val credential = prismIssuer.createCredential( + cred.credentialDefinition, + cred.credentialDefinitionPrivate, + credentialOffer, + credentialRequest.request, + credentialValues, + null, + null, + null + ) + println(credential.getJson()) + assertTrue(true) + } +} diff --git a/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismProverTests.kt b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismProverTests.kt new file mode 100644 index 00000000..75138109 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/commonTest/kotlin/io/iohk/atala/prism/anoncred/PrismProverTests.kt @@ -0,0 +1,54 @@ +package io.iohk.atala.prism.anoncred + +import anoncreds_wrapper.CredentialDefinitionConfig +import anoncreds_wrapper.Issuer +import anoncreds_wrapper.Prover +import anoncreds_wrapper.Schema +import anoncreds_wrapper.SignatureType +import kotlin.test.Test +import kotlin.test.assertTrue + +@AndroidIgnore +class PrismProverTests { + @Test + fun test_PrismProver_createLinkSecret() { + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + println(linkSecret.getBigNumber()) + println(linkSecret.getValue()) + assertTrue(linkSecret.getBigNumber().length > 0) + } + + @Test + fun test_PrismProver_createCredentialRequest() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer( + "did:web:xyz/resource/schema", + "did:web:xyz/resource/cred-def", + cred.credentialKeyCorrectnessProof + ) + + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + val credentialRequest = prismProver.createCredentialRequest( + "entropy", + null, + cred.credentialDefinition, + linkSecret, + "my-secret-id", + credentialOffer + ) + println(credentialRequest) + assertTrue(true) + } +} diff --git a/anoncred-kmm/anoncreds-kmp/src/jvmTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt b/anoncred-kmm/anoncreds-kmp/src/jvmTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt new file mode 100644 index 00000000..5298c83f --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/jvmTest/kotlin/io/iohk/atala/prism/anoncred/AndroidIgnore.kt @@ -0,0 +1,6 @@ +package io.iohk.atala.prism.anoncred + +/** + * Ignore Unit tests on Android Platform + */ +actual annotation class AndroidIgnore diff --git a/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds.def b/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds.def new file mode 100644 index 00000000..c9936d7c --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds.def @@ -0,0 +1 @@ +staticLibraries = libanoncreds.d \ No newline at end of file diff --git a/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds_wrapper.def b/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds_wrapper.def new file mode 100644 index 00000000..d3877794 --- /dev/null +++ b/anoncred-kmm/anoncreds-kmp/src/nativeInterop/cinterop/anoncreds_wrapper.def @@ -0,0 +1 @@ +staticLibraries = libanoncreds_wrapper.a \ No newline at end of file diff --git a/anoncred-kmm/build.gradle.kts b/anoncred-kmm/build.gradle.kts new file mode 100644 index 00000000..305c6e2c --- /dev/null +++ b/anoncred-kmm/build.gradle.kts @@ -0,0 +1,52 @@ +plugins { + id("org.jlleitschuh.gradle.ktlint") version "11.6.0" + kotlin("jvm") version "1.8.20" +} + +buildscript { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20") + classpath("com.android.tools.build:gradle:7.2.2") + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.21.0") + } +} + +allprojects { + repositories { + mavenCentral() + gradlePluginPortal() + google() + maven { url = uri("https://jitpack.io") } + } +} + +subprojects { + apply(plugin = "org.jlleitschuh.gradle.ktlint") + ktlint { + verbose.set(true) + outputToConsole.set(true) + filter { + val generatedCodePath = rootDir + .resolve("anoncreds-kmp") + .resolve("build") + .resolve("generated") + + exclude( + "$generatedCodePath/*/*", + "$generatedCodePath/*", + "$generatedCodePath/**", + "$generatedCodePath/**/**" + ) + exclude("**/generated/**") + exclude { projectDir.toURI().relativize(it.file.toURI()).path.contains("/generated/") } + exclude { element -> element.file.path.contains("generated/") } + exclude { it.file.path.contains("$buildDir/generated/") } + exclude { it.file.path.contains(layout.buildDirectory.dir("generated").get().toString()) } + } + } +} diff --git a/anoncred-kmm/gradle.properties b/anoncred-kmm/gradle.properties new file mode 100644 index 00000000..4cfeee0f --- /dev/null +++ b/anoncred-kmm/gradle.properties @@ -0,0 +1,12 @@ +# Kotlin +kotlin.code.style=official + +# KMP +kotlin.mpp.enableCInteropCommonization=true +kotlin.native.cacheKind.macosArm64=none +kotlin.mpp.androidSourceSetLayoutVersion=2 +kotlinx.atomicfu.enableJvmIrTransformation=true +kotlinx.atomicfu.enableJsIrTransformation=true + +# Android +android.useAndroidX=true diff --git a/anoncred-kmm/gradle/wrapper/gradle-wrapper.jar b/anoncred-kmm/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..ccebba7710deaf9f98673a68957ea02138b60d0a GIT binary patch literal 61608 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 literal 0 HcmV?d00001 diff --git a/anoncred-kmm/gradle/wrapper/gradle-wrapper.properties b/anoncred-kmm/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..f398c33c --- /dev/null +++ b/anoncred-kmm/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/anoncred-kmm/gradlew b/anoncred-kmm/gradlew new file mode 100755 index 00000000..79a61d42 --- /dev/null +++ b/anoncred-kmm/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/anoncred-kmm/gradlew.bat b/anoncred-kmm/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/anoncred-kmm/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/anoncred-kmm/settings.gradle.kts b/anoncred-kmm/settings.gradle.kts new file mode 100644 index 00000000..59a37349 --- /dev/null +++ b/anoncred-kmm/settings.gradle.kts @@ -0,0 +1,5 @@ +rootProject.name = "anoncreds-kmm-main" +include(":uniffi-kmm") +include(":anoncred-wrapper-rust") +include(":anoncreds-kmp") +include(":testapp") diff --git a/anoncred-kmm/testapp/.gitignore b/anoncred-kmm/testapp/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/anoncred-kmm/testapp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/anoncred-kmm/testapp/build.gradle.kts b/anoncred-kmm/testapp/build.gradle.kts new file mode 100644 index 00000000..412e0b39 --- /dev/null +++ b/anoncred-kmm/testapp/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +apply(plugin = "kotlinx-atomicfu") + +repositories { + mavenCentral() + gradlePluginPortal() + google() + maven { url = uri("https://jitpack.io") } +} + +android { + namespace = "com.example.testapp" + compileSdk = 33 + + defaultConfig { + applicationId = "com.example.testapp" + minSdk = 24 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + implementation("androidx.core:core-ktx:1.9.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.8.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + + implementation(project(":anoncreds-kmp")) + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} diff --git a/anoncred-kmm/testapp/proguard-rules.pro b/anoncred-kmm/testapp/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/anoncred-kmm/testapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/ExampleInstrumentedTest.kt b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..0078369a --- /dev/null +++ b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.example.testapp + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.testapp", appContext.packageName) + } +} diff --git a/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismIssuerTests.kt b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismIssuerTests.kt new file mode 100644 index 00000000..d50b7a41 --- /dev/null +++ b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismIssuerTests.kt @@ -0,0 +1,225 @@ +package com.example.testapp + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import anoncreds_wrapper.AttributeValues +import anoncreds_wrapper.CredentialDefinitionConfig +import anoncreds_wrapper.Issuer +import anoncreds_wrapper.Prover +import anoncreds_wrapper.RegistryType +import anoncreds_wrapper.Schema +import anoncreds_wrapper.SignatureType +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PrismIssuerTests { + @Test + fun test_PrismIssuer_createSchema() { + val expectedSchema = Schema("Moussa", "1.0", listOf("name", "age"), "sample:uri") + val scheme: Schema = Issuer().createSchema("Moussa", "1.0", "sample:uri", listOf("name", "age")) + assertEquals("scheme not equal", expectedSchema, scheme) + assertEquals("name not correct", expectedSchema.name, scheme.name) + assertEquals("version not correct", expectedSchema.version, scheme.version) + assertEquals("issuerId not correct", expectedSchema.issuerId, scheme.issuerId) + assertEquals("attrNames size is not correct", expectedSchema.attrNames.size, scheme.attrNames.size) + expectedSchema.attrNames.forEach { + assertTrue(scheme.attrNames.contains(it)) + } + } + + @Test + fun test_PrismIssuer_createCredentialDefinition() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema: Schema = prismIssuer.createSchema("Moussa", "1.0", "sample:uri", attributeNames) + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + println(cred.credentialDefinition.getJson()) + println(cred.credentialDefinitionPrivate.getJson()) + println(cred.credentialKeyCorrectnessProof.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createRevocationRegistryDef() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + println("regDef IssuerId: ${rev.regDef.getIssuerId()}") + println("regDef CredDefId: ${rev.regDef.getCredDefId()}") + println("regDefPrivate: ${rev.regDefPrivate.getJson()}") + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createRevocationStatusList() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + println(revStatusList.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_updateRevocationStatusListTimestampOnly() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + val updatedRevStatusList = prismIssuer.updateRevocationStatusListTimestampOnly(1000u, revStatusList) + println(updatedRevStatusList.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_updateRevocationStatusList() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val rev = prismIssuer.createRevocationRegistryDef( + cred.credentialDefinition, + "did:web:xyz/resource/cred-def", + "did:web:xyz", + "default-tag", + RegistryType.CL_ACCUM, + 1000u + ) + val revStatusList = prismIssuer.createRevocationStatusList( + "did:web:xyz/resource/rev-reg-def", + rev.regDef, + "did:web:xyz", + null, + true + ) + val updatedRevStatusList = prismIssuer.updateRevocationStatusList(null, listOf(1u), null, rev.regDef, revStatusList) + println(updatedRevStatusList.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createCredentialOffer() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer("did:web:xyz/resource/schema", "did:web:xyz/resource/cred-def", cred.credentialKeyCorrectnessProof) + println(credentialOffer.getJson()) + assertTrue(true) + } + + @Test + fun test_PrismIssuer_createCredential() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer("did:web:xyz/resource/schema", "did:web:xyz/resource/cred-def", cred.credentialKeyCorrectnessProof) + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + val credentialRequest = prismProver.createCredentialRequest("entropy", null, cred.credentialDefinition, linkSecret, "my-secret-id", credentialOffer) + val credentialValues = listOf(AttributeValues("name", "Moussa")) + val credential = prismIssuer.createCredential( + cred.credentialDefinition, + cred.credentialDefinitionPrivate, + credentialOffer, + credentialRequest.request, + credentialValues, + null, + null, + null + ) + println(credential.getJson()) + assertTrue(true) + } +} diff --git a/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismProverTests.kt b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismProverTests.kt new file mode 100644 index 00000000..f6c71372 --- /dev/null +++ b/anoncred-kmm/testapp/src/androidTest/java/com/example/testapp/PrismProverTests.kt @@ -0,0 +1,56 @@ +package com.example.testapp + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import anoncreds_wrapper.CredentialDefinitionConfig +import anoncreds_wrapper.Issuer +import anoncreds_wrapper.Prover +import anoncreds_wrapper.Schema +import anoncreds_wrapper.SignatureType +import junit.framework.TestCase.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PrismProverTests { + @Test + fun test_PrismProver_createLinkSecret() { + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + println(linkSecret.getBigNumber()) + println(linkSecret.getValue()) + assertTrue(linkSecret.getBigNumber().length > 0) + } + + @Test + fun test_PrismProver_createCredentialRequest() { + val prismIssuer = Issuer() + val attributeNames = listOf("name", "age") + val schema = Schema("Moussa", "1.0", attributeNames, "sample:uri") + val cred = prismIssuer.createCredentialDefinition( + "did:web:xyz/resource/schema", + schema, + "did:web:xyz", + "default-tag", + SignatureType.CL, + CredentialDefinitionConfig(true) + ) + val credentialOffer = prismIssuer.createCredentialOffer( + "did:web:xyz/resource/schema", + "did:web:xyz/resource/cred-def", + cred.credentialKeyCorrectnessProof + ) + + val prismProver = Prover() + val linkSecret = prismProver.createLinkSecret() + val credentialRequest = prismProver.createCredentialRequest( + "entropy", + null, + cred.credentialDefinition, + linkSecret, + "my-secret-id", + credentialOffer + ) + println(credentialRequest) + assertTrue(true) + } +} diff --git a/anoncred-kmm/testapp/src/main/AndroidManifest.xml b/anoncred-kmm/testapp/src/main/AndroidManifest.xml new file mode 100644 index 00000000..ceec6af8 --- /dev/null +++ b/anoncred-kmm/testapp/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/java/com/example/testapp/MainActivity.kt b/anoncred-kmm/testapp/src/main/java/com/example/testapp/MainActivity.kt new file mode 100644 index 00000000..b4b78b9c --- /dev/null +++ b/anoncred-kmm/testapp/src/main/java/com/example/testapp/MainActivity.kt @@ -0,0 +1,19 @@ +package com.example.testapp + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import anoncreds_wrapper.Prover + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + try { + val prover = Prover() + val linkSecret = prover.createLinkSecret() + println(linkSecret.getValue()) + } catch (ex: Throwable) { + throw ex + } + } +} diff --git a/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_background.xml b/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_foreground.xml b/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/layout/activity_main.xml b/anoncred-kmm/testapp/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..17eab17b --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp b/anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/anoncred-kmm/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp b/anoncred-kmm/testapp/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/anoncred-kmm/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/anoncred-kmm/testapp/src/main/res/values-night/themes.xml b/anoncred-kmm/testapp/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..f474bfe3 --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/values/colors.xml b/anoncred-kmm/testapp/src/main/res/values/colors.xml new file mode 100644 index 00000000..c8524cd9 --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/values/strings.xml b/anoncred-kmm/testapp/src/main/res/values/strings.xml new file mode 100644 index 00000000..ede419b6 --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + TestApp + \ No newline at end of file diff --git a/anoncred-kmm/testapp/src/main/res/values/themes.xml b/anoncred-kmm/testapp/src/main/res/values/themes.xml new file mode 100644 index 00000000..2248cb4b --- /dev/null +++ b/anoncred-kmm/testapp/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + +