taler-android

Android apps for GNU Taler (wallet, PoS, cashier)
Log | Files | Refs | README | LICENSE

commit eb41c615a64751d10554e2a28b99ec31172b1064
parent d8e127aed22f2f748982a0e8d43e5425fc2dfc4a
Author: Iván Ávalos <avalos@disroot.org>
Date:   Tue,  4 Feb 2025 13:15:09 +0100

[donau-verificator] add donau-verificator app

bug 0009477

Diffstat:
M.idea/gradle.xml | 1+
Adonau-verificator/.gitignore | 2++
Adonau-verificator/README.md | 26++++++++++++++++++++++++++
Adonau-verificator/build.gradle | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/invalid-qr.png | 0
Adonau-verificator/proguard-rules.pro | 22++++++++++++++++++++++
Adonau-verificator/src/main/AndroidManifest.xml | 44++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/cpp/CMakeLists.txt | 43+++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/cpp/verification.cpp | 634+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/ic_launcher-playstore.png | 0
Adonau-verificator/src/main/java/net/taler/donauverificator/MainActivity.java | 159+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/java/net/taler/donauverificator/Results.java | 166+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/libs/libsodium-1.0.19.0.aar | 0
Adonau-verificator/src/main/res/drawable-v24/ic_launcher_foreground.xml | 31+++++++++++++++++++++++++++++++
Adonau-verificator/src/main/res/drawable/barcode.png | 0
Adonau-verificator/src/main/res/drawable/ic_dashboard_black_24dp.xml | 9+++++++++
Adonau-verificator/src/main/res/drawable/ic_home_black_24dp.xml | 9+++++++++
Adonau-verificator/src/main/res/drawable/ic_launcher_background.xml | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/res/drawable/ic_notifications_black_24dp.xml | 9+++++++++
Adonau-verificator/src/main/res/drawable/ngi_taler_logo.png | 0
Adonau-verificator/src/main/res/layout/activity_main.xml | 16++++++++++++++++
Adonau-verificator/src/main/res/layout/fragment_results.xml | 101+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adonau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher.xml | 6++++++
Adonau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml | 6++++++
Adonau-verificator/src/main/res/mipmap-hdpi/ic_launcher.webp | 0
Adonau-verificator/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp | 0
Adonau-verificator/src/main/res/mipmap-hdpi/ic_launcher_round.webp | 0
Adonau-verificator/src/main/res/mipmap-mdpi/ic_launcher.webp | 0
Adonau-verificator/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp | 0
Adonau-verificator/src/main/res/mipmap-mdpi/ic_launcher_round.webp | 0
Adonau-verificator/src/main/res/mipmap-xhdpi/ic_launcher.webp | 0
Adonau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp | 0
Adonau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_round.webp | 0
Adonau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher.webp | 0
Adonau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp | 0
Adonau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp | 0
Adonau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher.webp | 0
Adonau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp | 0
Adonau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp | 0
Adonau-verificator/src/main/res/values-night/themes.xml | 17+++++++++++++++++
Adonau-verificator/src/main/res/values/colors.xml | 13+++++++++++++
Adonau-verificator/src/main/res/values/dimens.xml | 6++++++
Adonau-verificator/src/main/res/values/ic_launcher_background.xml | 5+++++
Adonau-verificator/src/main/res/values/strings.xml | 11+++++++++++
Adonau-verificator/src/main/res/values/themes.xml | 17+++++++++++++++++
Adonau-verificator/src/main/res/xml/backup_rules.xml | 14++++++++++++++
Adonau-verificator/src/main/res/xml/data_extraction_rules.xml | 20++++++++++++++++++++
Adonau-verificator/valid-qr.png | 0
Msettings.gradle | 1+
49 files changed, 1542 insertions(+), 0 deletions(-)

diff --git a/.idea/gradle.xml b/.idea/gradle.xml @@ -12,6 +12,7 @@ <set> <option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$/cashier" /> + <option value="$PROJECT_DIR$/donau-verificator" /> <option value="$PROJECT_DIR$/merchant-lib" /> <option value="$PROJECT_DIR$/merchant-terminal" /> <option value="$PROJECT_DIR$/taler-kotlin-android" /> diff --git a/donau-verificator/.gitignore b/donau-verificator/.gitignore @@ -0,0 +1 @@ +/build +\ No newline at end of file diff --git a/donau-verificator/README.md b/donau-verificator/README.md @@ -0,0 +1,25 @@ +# Donau Verify +The app verifies the donation statement made by a Donau. + +## Testing +1. With provided QR-Codes in the root app directory +2. For test purposes, a string of a valid donation statement is already hard coded. +3. With the defined URI scheme following command can be used: +```bash +adb shell am start -a android.intent.action.VIEW -d "donau://2024/EUR:15/7560001010000/1234/SAAM5BA1F9H4VT6T78CFC3X63HAMY2TXB597XBVZ0EMXEZ90QPJ3000BXDBJ3ECHGB8AEX9FFQ5BAXVSF6X6NXM98PY353F2R99PP1R/E24CDJHGSPZG20ZSSTMTBREGCCP495WKETQYCYA9C93EPMZN4FEG" +``` +## Future Work +The public key should be requested directly from the Donau over HTTPS, + for this the Donau base url is needed -> pass it with the QR code? + +## Building +### build requirements +- minimal Android SDK: 31 +- gradle +### build from command line +Mac OS, Linux: +- chmod +x gradlew +- ./gradlew + +Windows: +- gradlew.bat +\ No newline at end of file diff --git a/donau-verificator/build.gradle b/donau-verificator/build.gradle @@ -0,0 +1,79 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'net.taler.donauverificator' + compileSdk 34 + + defaultConfig { + applicationId "net.taler.donauverificator" + minSdk 30 + targetSdk 34 + versionCode 1 + versionName "1.0" + + archivesBaseName = "Donau-Verifcation" + externalNativeBuild { + cmake { + cppFlags '' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + buildFeatures { + viewBinding true + prefab true + } + + packagingOptions { + // Exclude specific files or directories that might cause conflicts + exclude 'META-INF/DEPENDENCIES' + exclude 'META-INF/LICENSE' + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE' + exclude 'META-INF/NOTICE.txt' + exclude 'META-INF/ASL2.0' + exclude 'META-INF/*.kotlin_module' + + // Merge specific files if needed + merge 'META-INF/LGPL2.1' + merge 'META-INF/AL2.0' + + // Exclude everything under META-INF to avoid conflicts + resources.excludes.add("META-INF/**/*") + } + externalNativeBuild { + cmake { + path file('src/main/cpp/CMakeLists.txt') + version '3.22.1' + } + } +} + +dependencies { + // TODO: do not vendor pre-built libsodium + implementation fileTree(dir: 'src/main/libs/', include: ['libsodium-1.0.19.0.aar']) + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' + implementation 'androidx.navigation:navigation-fragment:2.7.5' + implementation 'androidx.navigation:navigation-ui:2.7.5' + implementation 'com.github.yuriy-budiyev:code-scanner:2.3.2' + // https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on + implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '1.70' +} +\ No newline at end of file diff --git a/donau-verificator/invalid-qr.png b/donau-verificator/invalid-qr.png Binary files differ. diff --git a/donau-verificator/proguard-rules.pro b/donau-verificator/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 +\ No newline at end of file diff --git a/donau-verificator/src/main/AndroidManifest.xml b/donau-verificator/src/main/AndroidManifest.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools"> + + <uses-permission android:name="android.permission.CAMERA" + android:required="true"/> + + <uses-feature + android:name="android.hardware.camera" + android:required="false"/> + + <application + android:allowBackup="true" + android:dataExtractionRules="@xml/data_extraction_rules" + android:enableOnBackInvokedCallback="true" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:supportsRtl="true" + android:theme="@style/Theme.Verifaction" + tools:targetApi="31"> + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".Results" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="donau"/> + </intent-filter> + </activity> + </application> + +</manifest> +\ No newline at end of file diff --git a/donau-verificator/src/main/cpp/CMakeLists.txt b/donau-verificator/src/main/cpp/CMakeLists.txt @@ -0,0 +1,43 @@ + +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html. +# For more examples on how to use CMake, see https://github.com/android/ndk-samples. + +# Sets the minimum CMake version required for this project. +cmake_minimum_required(VERSION 3.22.1) + +# Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, +# Since this is the top level CMakeLists.txt, the project name is also accessible +# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level +# build script scope). +project("verification") + +# Creates and names a library, sets it as either STATIC +# or SHARED, and provides the relative paths to its source code. +# You can define multiple libraries, and CMake builds them for you. +# Gradle automatically packages shared libraries with your APK. +# +# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define +# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} +# is preferred for the same purpose. +# +# In order to load a library into your app from Java/Kotlin, you must call +# System.loadLibrary() and pass the name of the library defined here; +# for GameActivity/NativeActivity derived applications, the same library name must be +# used in the AndroidManifest.xml file. +add_library(${CMAKE_PROJECT_NAME} SHARED + # List C/C++ source files with relative paths to this CMakeLists.txt. + verification.cpp) + +find_package(sodium REQUIRED CONFIG) + +# Specifies libraries CMake should link to your target library. You +# can link libraries from various origins, such as libraries defined in this +# build script, prebuilt third-party libraries, or Android system libraries. +target_link_libraries(${CMAKE_PROJECT_NAME} + # List libraries link to the target library + android + sodium::sodium-static + log) + +# include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/libsodium/include ) diff --git a/donau-verificator/src/main/cpp/verification.cpp b/donau-verificator/src/main/cpp/verification.cpp @@ -0,0 +1,633 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +#include <jni.h> +#include <string> +#include <android/log.h> +#include <sys/endian.h> + +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "verification", __VA_ARGS__) + +// needed for libsodium +#include <sodium/crypto_sign.h> + +/** + * Maximum legal 'value' for an amount, based on IEEE double (for JavaScript compatibility). + */ +#define TALER_AMOUNT_MAX_VALUE (1LLU << 52) + +/** + * Define as empty, GNUNET_PACKED should suffice, but this won't work on W32 + */ +#define GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Define as empty, GNUNET_PACKED should suffice, but this won't work on W32; + */ +#define GNUNET_NETWORK_STRUCT_END + +/** + * gcc-ism to get packed structs. + */ +#define GNUNET_PACKED __attribute__ ((packed)) + +#define TALER_CURRENCY_LEN 12 + +/** + * @brief The "fraction" value in a `struct TALER_Amount` represents which + * fraction of the "main" value? + * + * Note that we need sub-cent precision here as transaction fees might + * be that low, and as we want to support microdonations. + * + * An actual `struct Amount a` thus represents + * "a.value + (a.fraction / #TALER_AMOUNT_FRAC_BASE)" units of "a.currency". + */ +#define TALER_AMOUNT_FRAC_BASE 100000000 + +enum GNUNET_GenericReturnValue +{ + GNUNET_SYSERR = -1, + GNUNET_NO = 0, + GNUNET_OK = 1, + /* intentionally identical to #GNUNET_OK! */ + GNUNET_YES = 1, +}; + +/** + * Public ECC key (always for curve Ed25519) encoded in a format + * suitable for network transmission and EdDSA signatures. Refer + * to section 5.1.3 of rfc8032, for a thorough explanation of how + * this value maps to the x- and y-coordinates. + */ +struct GNUNET_CRYPTO_EddsaPublicKey +{ + /** + * Point Q consists of a y-value mod p (256 bits); the x-value is + * always positive. The point is stored in Ed25519 standard + * compact format. + */ + unsigned char q_y[256 / 8]; +}; + +/** + * @brief an ECC signature using EdDSA. + * See cr.yp.to/papers.html#ed25519 + */ +struct GNUNET_CRYPTO_EddsaSignature +{ + /** + * R value. + */ + unsigned char r[256 / 8]; + + /** + * S value. + */ + unsigned char s[256 / 8]; +}; + +/** + * Regular online message signing key used by Donau. + */ +struct DONAU_DonauPublicKeyP +{ + /** + * Donau uses EdDSA for non-blind signing. + */ + struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; + +}; + +/** + * @brief Type of signature used by the donau for non-blind signatures. + */ +struct DONAU_DonauSignatureP +{ + /** + * Donau uses EdDSA for for non-blind signatures. + */ + struct GNUNET_CRYPTO_EddsaSignature eddsa_sig; +}; + +/** + * @brief header of what an ECC signature signs + * this must be followed by "size - 8" bytes of + * the actual signed data + */ +struct GNUNET_CRYPTO_EccSignaturePurpose +{ + /** + * How many bytes does this signature sign? + * (including this purpose header); in network + * byte order (!). + */ + uint32_t size GNUNET_PACKED; + + /** + * What does this signature vouch for? This + * must contain a GNUNET_SIGNATURE_PURPOSE_XXX + * constant (from gnunet_signatures.h). In + * network byte order! + */ + uint32_t purpose GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Amount, encoded for network transmission. + */ +struct TALER_AmountNBO +{ + /** + * Value in the main currency, in NBO. + */ + uint64_t value GNUNET_PACKED; + + /** + * Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE), in NBO. + */ + uint32_t fraction GNUNET_PACKED; + + /** + * Type of the currency being represented. Length is 12. + */ + char currency[TALER_CURRENCY_LEN]; +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * @brief Representation of monetary value in a given currency. + */ +struct TALER_Amount +{ + /** + * Value (numerator of fraction) + */ + uint64_t value; + + /** + * Fraction (integer multiples of #TALER_AMOUNT_FRAC_BASE). + */ + uint32_t fraction; + + /** + * Currency string, left adjusted and padded with zeros. All zeros + * for "invalid" values. + */ + char currency[TALER_CURRENCY_LEN]; +}; + +int +TALER_amount_is_valid (const struct TALER_Amount *amount) +{ + if (amount->value > TALER_AMOUNT_MAX_VALUE) + { + return -1; + } + return ('\0' != amount->currency[0]) ? 1 : 0; +} + +uint64_t +GNUNET_htonll (uint64_t n) +{ + return (((uint64_t) htonl (n)) << 32) + htonl (n >> 32); + //return n; +} + +void +TALER_amount_hton (struct TALER_AmountNBO *res, + const struct TALER_Amount *d) +{ + if (1 != TALER_amount_is_valid (d)) { + res = NULL; + return; + } + res->value = GNUNET_htonll (d->value); + res->fraction = htonl (d->fraction); + for (unsigned int i = 0; i<TALER_CURRENCY_LEN; i++) + res->currency[i] = d->currency[i]; +} + +/** + * Set @a a to "invalid". + * + * @param[out] a amount to set to invalid + */ +static void +invalidate (struct TALER_Amount *a) +{ + memset (a, + 0, + sizeof (struct TALER_Amount)); +} + +enum GNUNET_GenericReturnValue +TALER_check_currency (const char *str) +{ + if (strlen (str) >= TALER_CURRENCY_LEN) + { + return GNUNET_SYSERR; + } + /* validate str has only legal characters in it! */ + for (unsigned int i = 0; '\0' != str[i]; i++) + { + if ( ('A' > str[i]) || ('Z' < str[i]) ) + { + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + +enum GNUNET_GenericReturnValue +TALER_string_to_amount (const char *str, + struct TALER_Amount *amount) +{ + int n; + uint32_t b; + const char *colon; + const char *value; + + /* skip leading whitespace */ + while (isspace ( (unsigned char) str[0])) + str++; + if ('\0' == str[0]) + { + invalidate (amount); + return GNUNET_SYSERR; + } + + /* parse currency */ + colon = strchr (str, (int) ':'); + if ( (NULL == colon) || + (colon == str) || + ((colon - str) >= TALER_CURRENCY_LEN) ) + { + invalidate (amount); + return GNUNET_SYSERR; + } + + if (TALER_CURRENCY_LEN <= (colon - str)) return GNUNET_SYSERR; + for (unsigned int i = 0; i<colon - str; i++) + amount->currency[i] = str[i]; + /* 0-terminate *and* normalize buffer by setting everything to '\0' */ + memset (&amount->currency [colon - str], + 0, + TALER_CURRENCY_LEN - (colon - str)); + if (GNUNET_OK != + TALER_check_currency (amount->currency)) + return GNUNET_SYSERR; + /* skip colon */ + value = colon + 1; + if ('\0' == value[0]) + { + invalidate (amount); + return GNUNET_SYSERR; + } + + amount->value = 0; + amount->fraction = 0; + + /* parse value */ + while ('.' != *value) + { + if ('\0' == *value) + { + /* we are done */ + return GNUNET_OK; + } + if ( (*value < '0') || + (*value > '9') ) + { + invalidate (amount); + return GNUNET_SYSERR; + } + n = *value - '0'; + if ( (amount->value * 10 < amount->value) || + (amount->value * 10 + n < amount->value) || + (amount->value > TALER_AMOUNT_MAX_VALUE) || + (amount->value * 10 + n > TALER_AMOUNT_MAX_VALUE) ) + { + invalidate (amount); + return GNUNET_SYSERR; + } + amount->value = (amount->value * 10) + n; + value++; + } + + /* skip the dot */ + value++; + + + /* parse fraction */ + if ('\0' == *value) + { + invalidate (amount); + return GNUNET_SYSERR; + } + b = TALER_AMOUNT_FRAC_BASE / 10; + while ('\0' != *value) + { + if (0 == b) + { + invalidate (amount); + return GNUNET_SYSERR; + } + if ( (*value < '0') || + (*value > '9') ) + { + invalidate (amount); + return GNUNET_SYSERR; + } + n = *value - '0'; + amount->fraction += n * b; + b /= 10; + value++; + } + return GNUNET_OK; +} + +enum GNUNET_GenericReturnValue +TALER_string_to_amount_nbo (const char *str, + struct TALER_AmountNBO *amount_nbo) +{ + struct TALER_Amount amount; + + if (GNUNET_OK != + TALER_string_to_amount (str, + &amount)) + return GNUNET_SYSERR; + TALER_amount_hton (amount_nbo, + &amount); + return GNUNET_OK; +} + +/** + * @brief A 512-bit hashcode. These are the default length for GNUnet, using SHA-512. + */ +struct GNUNET_HashCode +{ + uint32_t bits[512 / 8 / sizeof(uint32_t)]; /* = 16 */ +}; + +/** + * Donor's hashed and salted unique donation identifier. + */ +struct DONAU_HashDonorTaxId +{ + unsigned char hash[512/8]; +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature/donation statement + * over the total amount and a donor identifier of a year. + */ +struct DONAU_DonationStatementConfirmationPS +{ + /** + * Purpose must be #DONAU_SIGNATURE_DONAU_DONATION_STATEMENT. Signed + * by a `struct DONAU_DonauPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Total amount donated of a specific @a year. + */ + struct TALER_AmountNBO amount_tot; + + /** + * The hash of the identifier of the donor. + */ + struct DONAU_HashDonorTaxId i; + + /** + * The corresponding year. + */ + uint32_t year; + +}; + +GNUNET_NETWORK_STRUCT_END + + +/** + * Get the decoded value corresponding to a character according to Crockford + * Base32 encoding. + * + * @param a a character + * @return corresponding numeric value + */ +static unsigned int +getValue__ (unsigned char a) +{ + unsigned int dec; + + switch (a) + { + case 'O': + case 'o': + a = '0'; + break; + + case 'i': + case 'I': + case 'l': + case 'L': + a = '1'; + break; + + /* also consider U to be V */ + case 'u': + case 'U': + a = 'V'; + break; + + default: + break; + } + if ((a >= '0') && (a <= '9')) + return a - '0'; + if ((a >= 'a') && (a <= 'z')) + a = toupper (a); + /* return (a - 'a' + 10); */ + dec = 0; + if ((a >= 'A') && (a <= 'Z')) + { + if ('I' < a) + dec++; + if ('L' < a) + dec++; + if ('O' < a) + dec++; + if ('U' < a) + dec++; + return(a - 'A' + 10 - dec); + } + return -1; +} + +int +GNUNET_STRINGS_string_to_data (const char *enc, + size_t enclen, + void *out, + size_t out_size) +{ + size_t rpos; + size_t wpos; + unsigned int bits; + unsigned int vbit; + int ret; + int shift; + unsigned char *uout; + size_t encoded_len; + + if (0 == enclen) + { + if (0 == out_size) + return 0; + return -1; + } + if (out_size >= SIZE_MAX) return -1; + encoded_len = out_size * 8; + uout = (unsigned char *) out; + wpos = out_size; + rpos = enclen; + if ((encoded_len % 5) > 0) + { + vbit = encoded_len % 5; /* padding! */ + shift = 5 - vbit; + bits = (ret = getValue__ (enc[--rpos])) >> shift; + } + else + { + vbit = 5; + shift = 0; + bits = (ret = getValue__ (enc[--rpos])); + } + if ((encoded_len + shift) / 5 != enclen) + return -1; + if (-1 == ret) + return -1; + while (wpos > 0) + { + if (0 == rpos) + { + return -1; + } + bits = ((ret = getValue__ (enc[--rpos])) << vbit) | bits; + if (-1 == ret) + return -1; + vbit += 5; + if (vbit >= 8) + { + uout[--wpos] = (unsigned char) bits; + bits >>= 8; + vbit -= 8; + } + } + if ((0 != rpos) || (0 != vbit)) + return -1; + return 0; +} + +extern "C" +JNIEXPORT jint JNICALL +Java_net_taler_donauverificator_Results_ed25519_1verify( + JNIEnv *env, jobject thiz, + jstring year, + jstring totalAmount, + jstring taxId, + jstring taxIdSalt, + jstring signature, + jstring public_key) { + + const char *const year_string = reinterpret_cast<const char *const>(env->GetStringUTFChars( + year, 0)); + const char *const s_str = reinterpret_cast<const char *const>(env->GetStringUTFChars( + signature, 0)); + const char *const pub_str = reinterpret_cast<const char *const>(env->GetStringUTFChars( + public_key, 0)); + const char* total_amount = env->GetStringUTFChars(totalAmount, 0); + const unsigned char* tax_id = reinterpret_cast<const unsigned char*>(env->GetStringUTFChars(taxId, 0)); + const unsigned char* salt = reinterpret_cast<const unsigned char*>(env->GetStringUTFChars(taxIdSalt, 0)); + + unsigned long long donation_year; + char dummy; + if ( (NULL == year_string) || + (1 != sscanf (year_string, + "%llu%c", + &donation_year, + &dummy)) ) + { + return -6; + } + + struct DONAU_DonauPublicKeyP pub; + struct DONAU_DonauSignatureP sig; + struct DONAU_HashDonorTaxId h_donor_tax_id; + + crypto_hash_sha512_state state; + crypto_hash_sha512_init(&state); + + unsigned int tax_length; + for (tax_length = 0; tax_id[tax_length]!= '\0'; ++tax_length); + unsigned int salt_length; + for (salt_length = 0; salt[salt_length]!= '\0'; ++salt_length); + + crypto_hash_sha512_update(&state, tax_id, tax_length); + crypto_hash_sha512_update(&state, salt, salt_length); + + crypto_hash_sha512_final(&state, h_donor_tax_id.hash); + + struct DONAU_DonationStatementConfirmationPS confirm = { + .purpose.purpose = htonl (1500), + .purpose.size = htonl (sizeof (confirm)), + .year = htonl (donation_year), + .i = h_donor_tax_id + }; + if (GNUNET_OK != TALER_string_to_amount_nbo (total_amount, + &confirm.amount_tot)) { + return -3; + } + if (0 != + GNUNET_STRINGS_string_to_data (s_str, + strlen (s_str), + &sig, + sizeof (sig))) + { + return -4; + } + if (0 != + GNUNET_STRINGS_string_to_data (pub_str, + strlen (pub_str), + &pub, + sizeof (pub))) + { + return -5; + } + size_t mlen = ntohl (confirm.purpose.size); + const unsigned char *m = (const unsigned char *) &confirm.purpose; + const unsigned char *s = (const unsigned char *) &sig.eddsa_sig; + const unsigned char *eddsa_pub = (const unsigned char*) &pub.eddsa_pub.q_y; + //verify function from libsodium (also used by GNUNET) + return crypto_sign_verify_detached (s, m, mlen, eddsa_pub); +} +\ No newline at end of file diff --git a/donau-verificator/src/main/ic_launcher-playstore.png b/donau-verificator/src/main/ic_launcher-playstore.png Binary files differ. diff --git a/donau-verificator/src/main/java/net/taler/donauverificator/MainActivity.java b/donau-verificator/src/main/java/net/taler/donauverificator/MainActivity.java @@ -0,0 +1,158 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +package net.taler.donauverificator; + +import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED; + +import android.Manifest; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.widget.Toast; + +import com.budiyev.android.codescanner.CodeScanner; +import com.budiyev.android.codescanner.CodeScannerView; +import com.budiyev.android.codescanner.DecodeCallback; +import com.google.zxing.Result; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import net.taler.donauverificator.databinding.ActivityMainBinding; + +public class MainActivity extends AppCompatActivity { + private int PERMISSIONS_REQUEST_CAMERA = 0; + private ActivityMainBinding binding; + private CodeScanner mCodeScanner; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + //Check if CAMERA permission is granted + if (ContextCompat.checkSelfPermission(this, "android.permission.CAMERA") == PERMISSION_GRANTED) { + } else { + askPermission(); + } + CodeScannerView scannerView = binding.scannerView; + mCodeScanner = new CodeScanner(this, scannerView); + mCodeScanner.setDecodeCallback(new DecodeCallback() { + @Override + public void onDecoded(@NonNull final Result result) { + runOnUiThread(new Runnable() { + @Override + public void run() { + sendRequestDialog(result.getText()); + } + }); + } + }); + //temporary for debugging + sendRequestDialog("donau://2024/EUR:15/7560001010000/1234/SAAM5BA1F9H4VT6T78CFC3X63HAMY2TXB597XBVZ0EMXEZ90QPJ3000BXDBJ3ECHGB8AEX9FFQ5BAXVSF6X6NXM98PY353F2R99PP1R/E24CDJHGSPZG20ZSSTMTBREGCCP495WKETQYCYA9C93EPMZN4FEG"); + + } + + @Override + public void onResume() { + super.onResume(); + mCodeScanner.startPreview(); + } + + @Override + public void onPause() { + mCodeScanner.releaseResources(); + super.onPause(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } + /** + * Asks user if app should proceed + * @param qrstring + */ + private void sendRequestDialog(String qrstring) { + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + //Yes button clicked + Intent intent = new Intent(getApplicationContext(), Results.class); + intent.putExtra("QR-String", qrstring); + startActivity(intent); + break; + + case DialogInterface.BUTTON_NEGATIVE: + //No button clicked + mCodeScanner.releaseResources(); + mCodeScanner.startPreview(); + break; + default: + throw new IllegalStateException("Unexpected value: " + which); + } + } + }; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("QR scanned: '" + qrstring + "'\nWould you like to proceed?").setPositiveButton("Yes", dialogClickListener) + .setNegativeButton("No", dialogClickListener).show(); + } + + /** + * Ask user to get camera permissions + * Handles permission lifecycle + */ + private void askPermission() { + + //Checks if a RequestPermissionRationale should be shown + if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { + + AlertDialog.Builder alert = new AlertDialog.Builder(getApplicationContext()); + alert.setTitle("Please grant camera permission"); + alert.setMessage("This APP needs the camera permission to scan QR codes."); + alert.setNeutralButton("OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + checkPermission("android.permission.CAMERA", PERMISSIONS_REQUEST_CAMERA); + } + }); + alert.setIcon(android.R.drawable.ic_dialog_alert); + alert.show(); + } else { + //A permission request should be performed + checkPermission("android.permission.CAMERA", PERMISSIONS_REQUEST_CAMERA); + } + } + + private void checkPermission(String permission, int requestCode) { + if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) { + ActivityCompat.requestPermissions(this, new String[] {permission}, requestCode); + } else { + Toast.makeText(this, "Permission already granted", Toast.LENGTH_SHORT).show(); + } + } + +} +\ No newline at end of file diff --git a/donau-verificator/src/main/java/net/taler/donauverificator/Results.java b/donau-verificator/src/main/java/net/taler/donauverificator/Results.java @@ -0,0 +1,166 @@ +/* + This file is part of TALER + Copyright (C) 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ + +package net.taler.donauverificator; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; +import android.widget.TableLayout; +import android.widget.TextView; + + +import androidx.appcompat.app.AppCompatActivity; + +import java.util.List; + +public class Results extends AppCompatActivity { + static { + System.loadLibrary("verification"); + } + + // QR-string : YEAR/TOTALAMOUNT/TAXID/TAXIDSALT/ED25519SIGNATURE/PUBKEY + // CrockfordBase32 encoded: SIGNATURE, PUBLICKEY + // TODO: Salt and taxId should maybe also be encoded + + private final int NUMBER_OF_ARGUMENTS = 6; + private String year; + private String totalAmount; + private String taxId; + private String salt; + private String eddsaSignature; + private String publicKey; + TextView sigStatusView; + TextView yearView; + TextView taxidView; + TextView totalView; + TableLayout tableLayout; + + public enum SignatureStatus { + INVALID_SCHEME, + INVALID_NUMBER_OF_ARGUMENTS, + MALFORMED_ARGUMENT, + SIGNATURE_INVALID, + SIGNATURE_VALID; + } + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.fragment_results); + sigStatusView = findViewById(R.id.sigStatus); + yearView = findViewById(R.id.year); + taxidView = findViewById(R.id.taxid); + totalView = findViewById(R.id.total); + tableLayout = findViewById(R.id.tableLayout); + tableLayout.setVisibility(View.INVISIBLE); + + Intent intent = getIntent(); + String scheme[]; + // handle URI scheme + if (null != intent.getData()) { + scheme = new String[2]; + Uri uri = intent.getData(); + List<String> pathSegments = uri.getPathSegments(); + StringBuilder fullPath = new StringBuilder(); + fullPath.append(uri.getHost()); + for (String segment : pathSegments) { + fullPath.append("/").append(segment); + } + scheme[1] = fullPath.toString(); + // handle self scanned QR code + } else { + scheme = intent.getStringExtra("QR-String").split("//"); + if (scheme == null || scheme.length != 2 || !scheme[0].equals("donau:")) { + statusHandling(SignatureStatus.INVALID_SCHEME); + return; + } + } + String[] parts = scheme[1].split("/"); + if (parts == null || parts.length != NUMBER_OF_ARGUMENTS) { + statusHandling(SignatureStatus.INVALID_NUMBER_OF_ARGUMENTS); + return; + } + + try { + year = parts[0]; + totalAmount = parts[1]; + taxId = parts[2]; + salt = parts[3]; + eddsaSignature = parts[4]; + publicKey = parts[5]; + } catch (Exception e) { + statusHandling(SignatureStatus.MALFORMED_ARGUMENT); + return; + } + + try { + checkSignature(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + private void checkSignature() throws Exception{ + + int res = ed25519_verify(year, totalAmount, taxId, + salt, eddsaSignature, publicKey); + System.out.println("Result: " + res); + if (res == 0) { + statusHandling(SignatureStatus.SIGNATURE_VALID); + } else { + statusHandling(SignatureStatus.SIGNATURE_INVALID); + } + } + + private void statusHandling(SignatureStatus es) { + View rootView = findViewById(R.id.root_view); + switch (es) { + case INVALID_SCHEME: + sigStatusView.setText(R.string.invalid_scheme); + rootView.setBackgroundResource(R.color.red); + break; + case INVALID_NUMBER_OF_ARGUMENTS: + sigStatusView.setText(R.string.invalid_number_of_arguments); + rootView.setBackgroundResource(R.color.red); + break; + case MALFORMED_ARGUMENT: + sigStatusView.setText(R.string.malformed_argument); + rootView.setBackgroundResource(R.color.red); + break; + case SIGNATURE_INVALID: + sigStatusView.setText(R.string.invalid_signature); + rootView.setBackgroundResource(R.color.red); + break; + case SIGNATURE_VALID: + tableLayout.setVisibility(View.VISIBLE); + sigStatusView.setText(R.string.valid_signature); + yearView.setText(year); + taxidView.setText(taxId); + totalView.setText(totalAmount); + rootView.setBackgroundResource(R.color.green); + break; + } + } + + public native int ed25519_verify(String year, String totalAmount, + String taxId, String salt, + String eddsaSignature, String publicKey); +} + diff --git a/donau-verificator/src/main/libs/libsodium-1.0.19.0.aar b/donau-verificator/src/main/libs/libsodium-1.0.19.0.aar Binary files differ. diff --git a/donau-verificator/src/main/res/drawable-v24/ic_launcher_foreground.xml b/donau-verificator/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> +</vector> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/drawable/barcode.png b/donau-verificator/src/main/res/drawable/barcode.png Binary files differ. diff --git a/donau-verificator/src/main/res/drawable/ic_dashboard_black_24dp.xml b/donau-verificator/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M3,13h8L11,3L3,3v10zM3,21h8v-6L3,15v6zM13,21h8L21,11h-8v10zM13,3v6h8L21,3h-8z" /> +</vector> diff --git a/donau-verificator/src/main/res/drawable/ic_home_black_24dp.xml b/donau-verificator/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z" /> +</vector> diff --git a/donau-verificator/src/main/res/drawable/ic_launcher_background.xml b/donau-verificator/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector + android:height="108dp" + android:width="108dp" + android:viewportHeight="108" + android:viewportWidth="108" + xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#3DDC84" + android:pathData="M0,0h108v108h-108z"/> + <path android:fillColor="#00000000" android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> + <path android:fillColor="#00000000" android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/> +</vector> diff --git a/donau-verificator/src/main/res/drawable/ic_notifications_black_24dp.xml b/donau-verificator/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.89,2 2,2zM18,16v-5c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2z" /> +</vector> diff --git a/donau-verificator/src/main/res/drawable/ngi_taler_logo.png b/donau-verificator/src/main/res/drawable/ngi_taler_logo.png Binary files differ. diff --git a/donau-verificator/src/main/res/layout/activity_main.xml b/donau-verificator/src/main/res/layout/activity_main.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="?attr/actionBarSize"> + + + <com.budiyev.android.codescanner.CodeScannerView + android:id="@+id/scanner_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + +</androidx.constraintlayout.widget.ConstraintLayout> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/layout/fragment_results.xml b/donau-verificator/src/main/res/layout/fragment_results.xml @@ -0,0 +1,100 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +android:id="@+id/root_view" +xmlns:tools="http://schemas.android.com/tools" +android:layout_width="match_parent" +android:layout_height="match_parent" +android:paddingStart="10dp" +android:paddingEnd="10dp" +android:orientation="vertical" +tools:context=".MainActivity"> + +<ImageView + android:id="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxWidth="200dp" + android:adjustViewBounds="true" + android:paddingBottom="30dp" + android:src="@drawable/ngi_taler_logo" + android:contentDescription="@string/NGI_TALER_logo" /> + +<TableLayout + android:id="@+id/tableLayout" + android:layout_below="@+id/image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:stretchColumns="1"> + + <!-- Row 1 --> + <TableRow + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textStyle="bold" + android:text="Year: " + android:textSize="18sp" /> + + <TextView + android:id="@+id/year" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="18sp" /> + </TableRow> + + <!-- Row 2 --> + <TableRow + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:textStyle="bold" + android:text="Tax Id: " + android:textSize="18sp" /> + + <TextView + android:id="@+id/taxid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="18sp" /> + </TableRow> + + <!-- Row 3 --> + <TableRow + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textStyle="bold" + android:text="Total Amount: " + android:textSize="18sp" /> + + <TextView + android:id="@+id/total" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="18sp" /> + </TableRow> + +</TableLayout> + +<TextView + android:id="@+id/sigStatus" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="20sp" + android:layout_centerInParent="true"/> +</RelativeLayout> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/donau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/donau-verificator/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@color/ic_launcher_background"/> + <foreground android:drawable="@mipmap/ic_launcher_foreground"/> +</adaptive-icon> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher.webp b/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/donau-verificator/src/main/res/mipmap-hdpi/ic_launcher_round.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher.webp b/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/donau-verificator/src/main/res/mipmap-mdpi/ic_launcher_round.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher.webp b/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/donau-verificator/src/main/res/mipmap-xhdpi/ic_launcher_round.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/donau-verificator/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp Binary files differ. diff --git a/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/donau-verificator/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp Binary files differ. diff --git a/donau-verificator/src/main/res/values-night/themes.xml b/donau-verificator/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Theme.Verifaction" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <!-- Primary brand color. --> + <item name="colorPrimary">@color/purple_200</item> + <item name="colorPrimaryVariant">@color/purple_700</item> + <item name="colorOnPrimary">@color/black</item> + <!-- Secondary brand color. --> + <item name="colorSecondary">@color/teal_200</item> + <item name="colorSecondaryVariant">@color/teal_200</item> + <item name="colorOnSecondary">@color/black</item> + <!-- Status bar color. --> + <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> + <!-- Customize your theme here. --> + </style> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/values/colors.xml b/donau-verificator/src/main/res/values/colors.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="purple_200">#FFBB86FC</color> + <color name="purple_500">#FF6200EE</color> + <color name="purple_700">#FF3700B3</color> + <color name="teal_200">#FF03DAC5</color> + <color name="teal_700">#FF018786</color> + <color name="black">#FF000000</color> + <color name="white">#FFFFFFFF</color> + <color name="red">#870C0C</color> + <color name="green">#045F06</color> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/values/dimens.xml b/donau-verificator/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/values/ic_launcher_background.xml b/donau-verificator/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="ic_launcher_background">#FFFFFF</color> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/values/strings.xml b/donau-verificator/src/main/res/values/strings.xml @@ -0,0 +1,10 @@ +<resources> + <string name="app_name">Donau Verify</string> + <string name="NGI_TALER_logo" >NGI Taler Logo</string> + <string name="with_adapter">List</string> + <string name="invalid_number_of_arguments">Invalid number of arguments!</string> + <string name="malformed_argument">Malformed argument!</string> + <string name="invalid_signature">Donation statement signature is invalid!</string> + <string name="valid_signature">Donation statement signature is valid!</string> + <string name="invalid_scheme">Invalid scheme!</string> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/values/themes.xml b/donau-verificator/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ +<resources xmlns:tools="http://schemas.android.com/tools"> + <!-- Base application theme. --> + <style name="Theme.Verifaction" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> + <!-- Primary brand color. --> + <item name="colorPrimary">@color/purple_500</item> + <item name="colorPrimaryVariant">@color/purple_700</item> + <item name="colorOnPrimary">@color/white</item> + <!-- Secondary brand color. --> + <item name="colorSecondary">@color/teal_200</item> + <item name="colorSecondaryVariant">@color/teal_700</item> + <item name="colorOnSecondary">@color/black</item> + <!-- Status bar color. --> + <item name="android:statusBarColor">?attr/colorPrimaryVariant</item> + <!-- Customize your theme here. --> + </style> +</resources> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/xml/backup_rules.xml b/donau-verificator/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample backup rules file; uncomment and customize as necessary. + See https://developer.android.com/guide/topics/data/autobackup + for details. + Note: This file is ignored for devices older that API 31 + See https://developer.android.com/about/versions/12/backup-restore +--> +<full-backup-content> + <!-- + <include domain="sharedpref" path="."/> + <exclude domain="sharedpref" path="device.xml"/> +--> +</full-backup-content> +\ No newline at end of file diff --git a/donau-verificator/src/main/res/xml/data_extraction_rules.xml b/donau-verificator/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + Sample data extraction rules file; uncomment and customize as necessary. + See https://developer.android.com/about/versions/12/backup-restore#xml-changes + for details. +--> +<data-extraction-rules> + <cloud-backup> + <!-- TODO: Use <include> and <exclude> to control what is backed up. + <include .../> + <exclude .../> + --> + </cloud-backup> + <!-- + <device-transfer> + <include .../> + <exclude .../> + </device-transfer> + --> +</data-extraction-rules> +\ No newline at end of file diff --git a/donau-verificator/valid-qr.png b/donau-verificator/valid-qr.png Binary files differ. diff --git a/settings.gradle b/settings.gradle @@ -1,3 +1,4 @@ +include ':donau-verificator' include ':cashier', ':merchant-terminal', ':wallet' include ':taler-kotlin-android' include ':merchant-lib'