donau

Donation authority for GNU Taler (experimental)
Log | Files | Refs | Submodules | README | LICENSE

commit 49836dab30f4bb23b9ae3e1931dc872ac8fd5bb5
parent 590d377aa124203c827d2fbe05718d04a519e604
Author: Matyja Lukas Adam <lukas.matyja@students.bfh.ch>
Date:   Tue, 18 Jun 2024 15:56:50 +0200

[verification-app] pass year, taxidhash and total amount as individual values

Diffstat:
Mverification-app/app/src/main/cpp/verification.cpp | 348++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mverification-app/app/src/main/java/taler/donau/verification/MainActivity.java | 8+++++---
Mverification-app/app/src/main/java/taler/donau/verification/Results.java | 81++++++++++++++++++++++++++-----------------------------------------------------
3 files changed, 343 insertions(+), 94 deletions(-)

diff --git a/verification-app/app/src/main/cpp/verification.cpp b/verification-app/app/src/main/cpp/verification.cpp @@ -24,12 +24,18 @@ // needed for libsodium #include "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) + extern "C" JNIEXPORT jstring JNICALL Java_taler_donau_verification_Results_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello from C++"; + LOGI("hhdh"); return env->NewStringUTF(hello.c_str()); } @@ -48,6 +54,28 @@ Java_taler_donau_verification_Results_stringFromJNI( */ #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 @@ -66,18 +94,6 @@ struct GNUNET_CRYPTO_EddsaPublicKey }; /** - * 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 an ECC signature using EdDSA. * See cr.yp.to/papers.html#ed25519 */ @@ -95,6 +111,18 @@ struct GNUNET_CRYPTO_EddsaSignature }; /** + * 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 @@ -148,12 +176,219 @@ struct TALER_AmountNBO /** * Type of the currency being represented. Length is 12. */ - char currency[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 @@ -169,6 +404,7 @@ struct DONAU_HashDonorTaxId struct GNUNET_HashCode hash; }; + GNUNET_NETWORK_STRUCT_BEGIN /** @@ -327,25 +563,73 @@ extern "C" JNIEXPORT jint JNICALL Java_taler_donau_verification_Results_ed25519_1verify( JNIEnv *env, jobject thiz, + jstring year, + jstring totalAmount, + jstring taxId, + jstring taxIdSalt, jstring signature, - jstring public_key, - jstring message) { + 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 *const m_str = reinterpret_cast<const char *const>(env->GetStringUTFChars( - message, 0)); + const char* total_amount = env->GetStringUTFChars(totalAmount, 0); + const char *const tax_id = reinterpret_cast<const char *const>(env->GetStringUTFChars(taxId, 0)); + //const char* salt = 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 GNUNET_HashContext *hash_context; + // hash_context = GNUNET_CRYPTO_hash_context_start (); + +/* GNUNET_CRYPTO_hash_context_read (hash_context, + tax_id, + sizeof((*tax_id))), + GNUNET_CRYPTO_hash_context_read (hash_context, + salt, + sizeof((*salt))); + GNUNET_CRYPTO_hash_context_finish (hash_context, + &h_donor_tax_id.hash);*/ + struct DONAU_DonauPublicKeyP pub; - struct DONAU_DonauSignatureP s; - struct DONAU_DonationStatementConfirmationPS m; + struct DONAU_DonauSignatureP sig; + struct DONAU_HashDonorTaxId h_donor_tax_id; + if (0 != + GNUNET_STRINGS_string_to_data (tax_id, + strlen (tax_id), + &h_donor_tax_id, + sizeof (h_donor_tax_id))) + { + return -2; + } + 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), - &s, - sizeof (s))) + &sig, + sizeof (sig))) { - return -1; + return -4; } if (0 != GNUNET_STRINGS_string_to_data (pub_str, @@ -353,20 +637,12 @@ Java_taler_donau_verification_Results_ed25519_1verify( &pub, sizeof (pub))) { - return -2; - } - if (0 != - GNUNET_STRINGS_string_to_data (m_str, - strlen (m_str), - &m, - sizeof (m))) - { - return -3; + return -5; } - size_t mlen = ntohl (m.purpose.size); - printf("%lu", mlen); - const unsigned char *mm = (const unsigned char *) &m.purpose; - const unsigned char *ss = (const unsigned char *) &s.eddsa_sig; + 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 (ss, mm, mlen, (const unsigned char*)&pub.eddsa_pub.q_y); + return crypto_sign_verify_detached (s, m, mlen, eddsa_pub); } \ No newline at end of file diff --git a/verification-app/app/src/main/java/taler/donau/verification/MainActivity.java b/verification-app/app/src/main/java/taler/donau/verification/MainActivity.java @@ -70,9 +70,11 @@ public class MainActivity extends AppCompatActivity { } }); //temporary for debugging should be a valid cleartext message for signing and the public key - sendRequestDialog("00000S00002XR000000000001W0000008NAN400000000000000AHAZC1CQTRFWWHM4C1CNGDSTYB4DPF9EBMHYC1W66CHMF3PVBBQDQAHGVAZN1W5ZHXE8BCBKCN7GWT94HWGW2JW4JH3GZ3XCJQJQ1M40001Z8:" + - "EN7JZPG9FXEZ2NNYGVK92FZVWPM16RF2A4Q56JDFG295442XSP4Y19PMZ34R8Q2F6D9EEBWD6YEJVC32QSK2C5EMXQ8RVKXM1BRG81R:" + - "E24CDJHGSPZG20ZSSTMTBREGCCP495WKETQYCYA9C93EPMZN4FEG"); + //sendRequestDialog("00000S00002XR000000000001W0000008NAN400000000000000AHAZC1CQTRFWWHM4C1CNGDSTYB4DPF9EBMHYC1W66CHMF3PVBBQDQAHGVAZN1W5ZHXE8BCBKCN7GWT94HWGW2JW4JH3GZ3XCJQJQ1M40001Z8:" + + // "EN7JZPG9FXEZ2NNYGVK92FZVWPM16RF2A4Q56JDFG295442XSP4Y19PMZ34R8Q2F6D9EEBWD6YEJVC32QSK2C5EMXQ8RVKXM1BRG81R:" + + // "E24CDJHGSPZG20ZSSTMTBREGCCP495WKETQYCYA9C93EPMZN4FEG"); + sendRequestDialog("2024/EUR:15/N2NYR2SFNGZSS388R2SB0VKNWP8VCYJWQ93WR3RCCS38Y7DPPQEVEN31PNZA3RBZ3TWGPRQ6SAF1SMJ93S1R55R95271Y7TS5F5E388/EN7JZPG9FXEZ2NNYGVK92FZVWPM16RF2A4Q56JDFG295442XSP4Y19PMZ34R8Q2F6D9EEBWD6YEJVC32QSK2C5EMXQ8RVKXM1BRG81R/E24CDJHGSPZG20ZSSTMTBREGCCP495WKETQYCYA9C93EPMZN4FEG"); + } @Override diff --git a/verification-app/app/src/main/java/taler/donau/verification/Results.java b/verification-app/app/src/main/java/taler/donau/verification/Results.java @@ -24,27 +24,28 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import java.io.ByteArrayOutputStream; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + public class Results extends AppCompatActivity { static { System.loadLibrary("verification"); } - // QR-string: YEAR:AMOUNT:FRACTION:TAXID:TAXIDSALT:EDD25519SIGNATURE - // Base64 encoded: SALT,TAXID,SIGNATURE, PUBLICKEY (public key only temporary for testing) - // The public key should be requested directly from the Donau over HTTPS + // QR-string : YEAR/TOTALAMOUNT/TAXID/TAXIDSALT/ED25519SIGNATURE/PUBKEY + // CrockfordBase32 encoded: TAXID, SALT, SIGNATURE, PUBLICKEY + // FIXME: The public key should be requested directly from the Donau over HTTPS - private final int NUMBER_OF_ARGUMENTS = 3; + private final int NUMBER_OF_ARGUMENTS = 5; // hard coded (only temporary for testing) private final int SIGNATURECODE = 1500; // uint64_t - private Long amount; - // uint32_t - private int fraction; - // uint64_t - private Long year; - private byte[] taxid; - private byte[] salt; - private String message; + private String year; + private String totalAmount; + private String taxId; + private String salt; private String eddsaSignature; private String publicKey; TextView textView; @@ -62,25 +63,22 @@ public class Results extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_results); textView = findViewById(R.id.textView); - textView.setText(stringFromJNI()); + //textView.setText(stringFromJNI()); Intent intent = getIntent(); textView.setText(intent.getStringExtra("QR-String")); - String[] parts = intent.getStringExtra("QR-String").split(":"); + String[] parts = intent.getStringExtra("QR-String").split("/"); if (parts == null || parts.length != NUMBER_OF_ARGUMENTS) { statusHandling(SignatureStatus.INVALID_NUMBER_OF_ARGUMENTS); return; } try { - /*year = Long.parseUnsignedLong(parts[0]); - amount = Long.parseUnsignedLong(parts[1]); - fraction = Integer.parseInt(parts[2]); - taxid = Base64.getDecoder().decode(parts[3]); - salt = Base64.getDecoder().decode(parts[4]);*/ - message = parts[0]; - eddsaSignature = parts[1]; - publicKey = parts[2]; + year = parts[0]; + totalAmount = parts[1]; + taxId = parts[2]; + eddsaSignature = parts[3]; + publicKey = parts[4]; } catch (Exception e) { statusHandling(SignatureStatus.MALFORMED_ARGUMENT); return; @@ -94,21 +92,11 @@ public class Results extends AppCompatActivity { } - // FIXME: Maybe simpler with java native?! private void checkSignature() throws Exception{ - /*AsymmetricKeyParameter publicKeyParameters = OpenSSHPublicKeyUtil.parsePublicKey(publicKey); - Signer verifier = new Ed25519Signer(); - verifier.init(false, publicKeyParameters); - byte[] yearBytes = parseLongToBytes(year);*/ - // TODO: hash the salted tax id to a byte array - // TODO: translate the amount to TALER AMOUNT NBO - // use ByteBuffer.order(ByteOrder.BIG_ENDIAN) to change byteorder - - //String message = ""; // ..... - //Long messageLength = Long.valueOf(100); - int res = ed25519_verify(eddsaSignature, publicKey, message); + + int res = ed25519_verify(year, totalAmount, taxId, + salt, eddsaSignature, publicKey); System.out.println("Result: " + res); - //System.out.println(messageLength); if (res == 0) { statusHandling(SignatureStatus.SIGNATURE_VALID); } else { @@ -138,26 +126,9 @@ public class Results extends AppCompatActivity { } } - private byte[] parseIntToBytes(int intValue) { - return new byte[] { - (byte)(intValue >> 24), - (byte)(intValue >> 16), - (byte)(intValue >> 8), - (byte)intValue}; - } - - private byte[] parseLongToBytes(Long longValue) { - return new byte[] { - (byte)(longValue >> 56), - (byte)(longValue >> 48), - (byte)(longValue >> 40), - (byte)(longValue >> 32), - (byte)(longValue >> 24), - (byte)(longValue >> 16), - (byte)(longValue >> 8), - (byte) (longValue >> 0)}; - } public native String stringFromJNI(); - public native int ed25519_verify(String eddsaSignature, String publicKey, String message); + public native int ed25519_verify(String year, String totalAmount, + String taxId, String salt, + String eddsaSignature, String publicKey); }