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:
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);
}