diff options
-rw-r--r-- | src/exchange/taler-exchange-httpd_age-withdraw_reveal.c | 96 | ||||
-rw-r--r-- | src/include/taler_crypto_lib.h | 48 | ||||
-rw-r--r-- | src/util/age_restriction.c | 268 | ||||
-rw-r--r-- | src/util/crypto.c | 35 |
4 files changed, 342 insertions, 105 deletions
diff --git a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c index cdb8115f8..b378e0e2b 100644 --- a/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c +++ b/src/exchange/taler-exchange-httpd_age-withdraw_reveal.c @@ -35,7 +35,8 @@ struct AgeRevealContext { /** - * Commitment for the age-withdraw operation. + * Commitment for the age-withdraw operation, previously called by the + * client. */ struct TALER_AgeWithdrawCommitmentHashP ach; @@ -63,11 +64,6 @@ struct AgeRevealContext struct TEH_DenominationKey *denom_keys; /** - * #num_coins hases of blinded coins. - */ - struct TALER_BlindedCoinHashP *coin_evs; - - /** * Total sum of all denominations' values **/ struct TALER_Amount total_amount; @@ -78,6 +74,11 @@ struct AgeRevealContext struct TALER_Amount total_fee; /** + * #num_coins hashes of blinded coins. + */ + struct TALER_BlindedCoinHashP *coin_evs; + + /** * #num_coins*(kappa - 1) disclosed coins. */ struct GNUNET_CRYPTO_EddsaPrivateKey *disclosed_coins; @@ -343,17 +344,15 @@ denomination_is_valid ( connection, result); - /* Does the denomination exist? */ if (NULL == dks) { + /* The denomination doesn't exist */ GNUNET_assert (result != NULL); /* Note: a HTTP-response has been queued and result has been set by * TEH_keys_denominations_by_hash2 */ return false; } - /* Is the denomation still and already valid? */ - if (GNUNET_TIME_absolute_is_past (dks->meta.expire_withdraw.abs_time)) { /* This denomination is past the expiration time for withdraws */ @@ -504,7 +503,7 @@ are_denominations_valid ( if (0 != TALER_amount_cmp (&sum, amount_with_fee)) { - GNUNET_break (0); + GNUNET_break_op (0); *result = TALER_MHD_reply_with_ec (connection, TALER_EC_EXCHANGE_AGE_WITHDRAW_AMOUNT_INCORRECT, NULL); @@ -516,6 +515,62 @@ are_denominations_valid ( } +/** + * Checks the validity of the disclosed coins as follows: + * - Derives and calculates the disclosed coins' + * - public keys, + * - nonces (if applicable), + * - age commitments, + * - blindings + * - blinded hashes + * - Computes h_commitment with those calculated and the undisclosed hashes + * - Compares h_commitment with the value from the original commitment + * - Verifies that all public keys in indices larger than max_age_group are + * derived from the constant public key. + * + * The derivation of the blindings, (potential) nonces and age-commitment from + * a coin's private keys is defined in + * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw + * + * @param connection HTTP-connection to the client + * @param h_commitment_orig Original commitment + * @param max_age_group Maximum age group allowed for the age restriction + * @param noreveal_idx Index that was given to the client in response to the age-withdraw request + * @param num_coins Number of coins + * @param coin_evs The Hashes of the undisclosed, blinded coins + * @param discloded_coins The private keys of the disclosed coins + * @param denom_keys The array of denomination keys. Needed to detect Clause-Schnorr-based denominations + * @param[out] result On error, a HTTP-response will be queued and result set accordingly + * @return GNUNET_OK on success, GNUNET_SYSERR otherwise + */ +static enum GNUNET_GenericReturnValue +verify_commitment_and_max_age ( + struct MHD_Connection *connection, + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment_orig, + uint32_t max_age_group, + uint32_t noreveal_idx, + uint32_t num_coins, + const struct TALER_BlindedCoinHashP *coin_evs, + const struct GNUNET_CRYPTO_EddsaPrivateKey *disclosed_coins, + const struct TEH_DenominationKey *denom_keys, + MHD_RESULT *result) +{ + struct TALER_BlindedCoinHashP *disclosed_evs; /* Will contain all calculated hashes */ + + disclosed_evs = GNUNET_new_array ( + num_coins * (TALER_CNC_KAPPA - 1), + struct TALER_BlindedCoinHashP); + + for (uint32_t i = 0; i < num_coins; i++) + { + /* FIXME:oec: Calculate new coins and blinded hashes */ + /*TALER_age_restriction_commit_from_base(); */ + } + + return GNUNET_SYSERR; +} + + MHD_RESULT TEH_handler_age_withdraw_reveal ( struct TEH_RequestContext *rc, @@ -583,15 +638,24 @@ TEH_handler_age_withdraw_reveal ( &result)) break; + /* Verify the computed h_commitment equals the committed one and that + * coins have a maximum age group of max_age_group */ + if (GNUNET_OK != verify_commitment_and_max_age ( + rc->connection, + &actx.commitment.h_commitment, + actx.commitment.max_age_group, + actx.commitment.noreveal_index, + actx.num_coins, + actx.coin_evs, + actx.disclosed_coins, + actx.denom_keys, + &result)) + break; + + /* TODO:oec: sign the coins */ } while(0); - /* TODO:oec: compute the disclosed blinded coins */ - /* TODO:oec: generate h_commitment_comp */ - /* TODO:oec: compare h_commitment_comp against h_commitment */ - /* TODO:oec: sign the coins */ - /* TODO:oec: send response */ - age_reveal_context_free (&actx); GNUNET_JSON_parse_free (spec); return result; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index db50efa10..f7462d093 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -18,6 +18,7 @@ * @brief taler-specific crypto functions * @author Sree Harsha Totakura <sreeharsha@totakura.in> * @author Christian Grothoff <christian@grothoff.org> + * @author Özgür Kesim <oec-taler@kesim.org> */ #if ! defined (__TALER_UTIL_LIB_H_INSIDE__) #error "Only <taler_util.h> can be included directly." @@ -1309,13 +1310,8 @@ struct TALER_AgeAttestation #endif }; - -extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash; -#define TALER_AgeCommitmentHash_isNullOrZero(ph) \ - ((NULL == ph) || \ - (0 == memcmp (ph, \ - &TALER_ZeroAgeCommitmentHash, \ - sizeof(struct TALER_AgeCommitmentHash)))) +#define TALER_AgeCommitmentHash_isNullOrZero(ph) ((NULL == ph) || \ + GNUNET_is_zero (ph)) /** * @brief Type of public signing keys for verifying blindly signed coins. @@ -5931,4 +5927,42 @@ void TALER_age_commitment_proof_free ( struct TALER_AgeCommitmentProof *p); + +/** + * @brief For age-withdraw, clients have to prove that the public keys for all + * age groups larger than the allowed maximum age group are derived by scalar + * multiplication from this Edx25519 public key (in Crockford Base32 encoding): + * + * DZJRF6HXN520505XDAWM8NMH36QV9J3VH77265WQ09EBQ76QSKCG + * + * Its private key was chosen randomly and then deleted. + */ +extern struct +#ifndef AGE_RESTRICTION_WITH_ECDSA +GNUNET_CRYPTO_Edx25519PublicKey +#else +GNUNET_CRYPTO_EcdsaPublicKey +#endif +TALER_age_commitment_base_public_key; + +/** + * @brief Similiar to TALER_age_restriction_commit, but takes the coin's + * private key as seed input and calculates the public keys in the slots larger + * than the given age as derived from TALER_age_commitment_base_public_key. + * + * See https://docs.taler.net/core/api-exchange.html#withdraw-with-age-restriction + * + * @param mask The age mask, defining the age groups + * @param max_age The maximum age for this coin. + * @param coin_priv The private key of the coin from which we derive the age restriction + * @param[out] comm_proof The commitment and proof for age restriction for age @a max_age + */ +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit_from_base ( + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_AgeMask *mask, + uint8_t max_age, + struct TALER_AgeCommitmentProof *comm_proof); + + #endif diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c index f4ac9abea..594e1d464 100644 --- a/src/util/age_restriction.c +++ b/src/util/age_restriction.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2022 Taler Systems SA + Copyright (C) 2022-2023 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 @@ -24,6 +24,19 @@ #include <gnunet/gnunet_json_lib.h> #include <gcrypt.h> +struct +#ifndef AGE_RESTRICTION_WITH_ECDSA +GNUNET_CRYPTO_Edx25519PublicKey +#else +GNUNET_CRYPTO_EcdsaPublicKey +#endif +TALER_age_commitment_base_public_key = { + .q_y = { 0x6f, 0xe5, 0x87, 0x9a, 0x3d, 0xa9, 0x44, 0x20, + 0x80, 0xbd, 0x6a, 0xb9, 0x44, 0x56, 0x91, 0x19, + 0xaf, 0xb4, 0xc8, 0x7b, 0x89, 0xce, 0x23, 0x17, + 0x97, 0x20, 0x5c, 0xbb, 0x9c, 0xd7, 0xcc, 0xd9}, +}; + void TALER_age_commitment_hash ( const struct TALER_AgeCommitment *commitment, @@ -82,36 +95,78 @@ get_age_group ( } +#ifdef AGE_RESTRICTION_WITH_ECDSA +/* @brief Helper function to generate a ECDSA private key + * + * @param seed Input seed + * @param size Size of the seed in bytes + * @param[out] pkey ECDSA private key + * @return GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +ecdsa_create_from_seed ( + const void *seed, + size_t seed_size, + struct GNUNET_CRYPTO_EcdsaPrivateKey *key) +{ + enum GNUNET_GenericReturnValue ret; + ret = GNUNET_CRYPTO_kdf (key, + sizeof (*key), + &seed, + seed_size, + "age commitment", + sizeof ("age commitment") - 1, + NULL, 0); + if (GNUNET_OK != ret) + return ret; + + /* See GNUNET_CRYPTO_ecdsa_key_create */ + key->d[0] &= 248; + key->d[31] &= 127; + key->d[31] |= 64; + + return GNUNET_OK; +} + + +#endif + + enum GNUNET_GenericReturnValue TALER_age_restriction_commit ( const struct TALER_AgeMask *mask, const uint8_t age, const struct GNUNET_HashCode *seed, - struct TALER_AgeCommitmentProof *new) + struct TALER_AgeCommitmentProof *ncp) { struct GNUNET_HashCode seed_i; - uint8_t num_pub = __builtin_popcount (mask->bits) - 1; - uint8_t num_priv = get_age_group (mask, age); + uint8_t num_pub; + uint8_t num_priv; size_t i; + GNUNET_assert (NULL != mask); GNUNET_assert (NULL != seed); - GNUNET_assert (NULL != new); + GNUNET_assert (NULL != ncp); GNUNET_assert (mask->bits & 1); /* fist bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = get_age_group (mask, age); + GNUNET_assert (31 > num_priv); GNUNET_assert (num_priv <= num_pub); seed_i = *seed; - new->commitment.mask.bits = mask->bits; - new->commitment.num = num_pub; - new->proof.num = num_priv; - new->proof.keys = NULL; + ncp->commitment.mask.bits = mask->bits; + ncp->commitment.num = num_pub; + ncp->proof.num = num_priv; + ncp->proof.keys = NULL; - new->commitment.keys = GNUNET_new_array ( + ncp->commitment.keys = GNUNET_new_array ( num_pub, struct TALER_AgeCommitmentPublicKeyP); if (0 < num_priv) - new->proof.keys = GNUNET_new_array ( + ncp->proof.keys = GNUNET_new_array ( num_priv, struct TALER_AgeCommitmentPrivateKeyP); @@ -126,47 +181,33 @@ TALER_age_restriction_commit ( /* Only save the private keys for age groups less than num_priv */ if (i < num_priv) - pkey = &new->proof.keys[i]; + pkey = &ncp->proof.keys[i]; #ifndef AGE_RESTRICTION_WITH_ECDSA GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i, sizeof(seed_i), &pkey->priv); GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv, - &new->commitment.keys[i].pub); - seed_i.bits[0] += 1; - } - - return GNUNET_OK; + &ncp->commitment.keys[i].pub); #else - if (GNUNET_OK != - GNUNET_CRYPTO_kdf (pkey, - sizeof (*pkey), - &salti, - sizeof (salti), - "age commitment", - strlen ("age commitment"), - NULL, 0)) - goto FAIL; - - /* See GNUNET_CRYPTO_ecdsa_key_create */ - pkey->priv.d[0] &= 248; - pkey->priv.d[31] &= 127; - pkey->priv.d[31] |= 64; + if (GNUNET_OK != + ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv)) + { + GNUNET_free (ncp->commitment.keys); + GNUNET_free (ncp->proof.keys); + return GNUNET_SYSERR; + } GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, - &new->commitment.keys[i].pub); + &ncp->commitment.keys[i].pub); +#endif + seed_i.bits[0] += 1; } return GNUNET_OK; - -FAIL: - GNUNET_free (new->commitment.keys); - if (NULL != new->proof.keys) - GNUNET_free (new->proof.keys); - return GNUNET_SYSERR; -#endif } @@ -216,33 +257,30 @@ TALER_age_commitment_derive ( &newacp->proof.keys[i].priv); } #else - char label[sizeof(uint64_t) + 1] = {0}; - - /* Because GNUNET_CRYPTO_ecdsa_public_key_derive expects char * (and calls - * strlen on it), we must avoid 0's in the label. */ - uint64_t nz_salt = salt | 0x8040201008040201; - memcpy (label, &nz_salt, sizeof(nz_salt)); - - /* 1. Derive the public keys */ - for (size_t i = 0; i < orig->commitment.num; i++) { - GNUNET_CRYPTO_ecdsa_public_key_derive ( - &orig->commitment.keys[i].pub, - label, - "age commitment derive", - &newacp->commitment.keys[i].pub); - } + const char *label = GNUNET_h2s (salt); - /* 2. Derive the private keys */ - for (size_t i = 0; i < orig->proof.num; i++) - { - struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; - priv = GNUNET_CRYPTO_ecdsa_private_key_derive ( - &orig->proof.keys[i].priv, - label, - "age commitment derive"); - newacp->proof.keys[i].priv = *priv; - GNUNET_free (priv); + /* 1. Derive the public keys */ + for (size_t i = 0; i < orig->commitment.num; i++) + { + GNUNET_CRYPTO_ecdsa_public_key_derive ( + &orig->commitment.keys[i].pub, + label, + "age commitment derive", + &newacp->commitment.keys[i].pub); + } + + /* 2. Derive the private keys */ + for (size_t i = 0; i < orig->proof.num; i++) + { + struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; + priv = GNUNET_CRYPTO_ecdsa_private_key_derive ( + &orig->proof.keys[i].priv, + label, + "age commitment derive"); + newacp->proof.keys[i].priv = *priv; + GNUNET_free (priv); + } } #endif @@ -546,4 +584,106 @@ TALER_age_mask_to_string ( } +enum GNUNET_GenericReturnValue +TALER_age_restriction_commit_from_base ( + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_AgeMask *mask, + uint8_t max_age, + struct TALER_AgeCommitmentProof *ncp) +{ + struct GNUNET_HashCode seed_i = {0}; + uint8_t num_pub; + uint8_t num_priv; + + GNUNET_assert (NULL != mask); + GNUNET_assert (NULL != coin_priv); + GNUNET_assert (NULL != ncp); + GNUNET_assert (mask->bits & 1); /* fist bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = get_age_group (mask, max_age); + + GNUNET_assert (31 > num_priv); + GNUNET_assert (num_priv <= num_pub); + + ncp->commitment.mask.bits = mask->bits; + ncp->commitment.num = num_pub; + ncp->proof.num = num_priv; + ncp->proof.keys = NULL; + + ncp->commitment.keys = GNUNET_new_array ( + num_pub, + struct TALER_AgeCommitmentPublicKeyP); + + if (0 < num_priv) + ncp->proof.keys = GNUNET_new_array ( + num_priv, + struct TALER_AgeCommitmentPrivateKeyP); + + /* Create as many private keys as allow with max_age and derive the + * corresponding public keys. The rest of the needed public keys are created + * by scalar mulitplication with the TALER_age_commitment_base_public_key. */ + for (size_t i = 0; i < num_pub; i++) + { + enum GNUNET_GenericReturnValue ret; + const char *label = i < num_priv ? "age-commitment" : "age-factor"; + + ret = GNUNET_CRYPTO_kdf (&seed_i, sizeof(seed_i), + coin_priv, sizeof(*coin_priv), + label, strlen (label), + &i, sizeof(i), + NULL, 0); + GNUNET_assert (GNUNET_OK == ret); + + /* Only generate and save the private keys and public keys for age groups + * less than num_priv */ + if (i < num_priv) + { + struct TALER_AgeCommitmentPrivateKeyP *pkey = &ncp->proof.keys[i]; + +#ifndef AGE_RESTRICTION_WITH_ECDSA + GNUNET_CRYPTO_edx25519_key_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); + GNUNET_CRYPTO_edx25519_key_get_public (&pkey->priv, + &ncp->commitment.keys[i].pub); +#else + if (GNUNET_OK != ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv)) + { + GNUNET_free (ncp->commitment.keys); + GNUNET_free (ncp->proof.keys); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, + &ncp->commitment.keys[i].pub); +#endif + } + else + { + /* For all indices larger than num_priv, derive a public key from + * TALER_age_commitment_base_public_key by scalar multiplication */ +#ifndef AGE_RESTRICTION_WITH_ECDSA + GNUNET_CRYPTO_edx25519_public_key_derive ( + &TALER_age_commitment_base_public_key, + &seed_i, + sizeof(seed_i), + &ncp->commitment.keys[i].pub); +#else + + GNUNET_CRYPTO_ecdsa_public_key_derive ( + &TALER_age_commitment_base_public_key, + GNUNET_h2s (&seed_i), + "age withdraw", + &ncp->commitment.keys[i].pub); +#endif + } + } + + return GNUNET_OK; + +} + + /* end util/age_restriction.c */ diff --git a/src/util/crypto.c b/src/util/crypto.c index 5cbba8135..bb14b6cdc 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -27,11 +27,6 @@ #include <gcrypt.h> /** - * Used in TALER_AgeCommitmentHash_isNullOrZero for comparison - */ -const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash = {0}; - -/** * Function called by libgcrypt on serious errors. * Prints an error message and aborts the process. * @@ -426,19 +421,23 @@ TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, { /* Coin comes with age commitment. Take the hash of the age commitment * into account */ - const size_t key_s = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey); - const size_t age_s = sizeof(struct TALER_AgeCommitmentHash); - char data[key_s + age_s]; - - GNUNET_memcpy (&data[0], - &coin_pub->eddsa_pub, - key_s); - GNUNET_memcpy (&data[key_s], - ach, - age_s); - GNUNET_CRYPTO_hash (&data, - key_s + age_s, - &coin_h->hash); + struct GNUNET_HashContext *hash_context; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + + GNUNET_CRYPTO_hash_context_read ( + hash_context, + &coin_pub->eddsa_pub, + sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey)); + + GNUNET_CRYPTO_hash_context_read ( + hash_context, + ach, + sizeof(struct TALER_AgeCommitmentHash)); + + GNUNET_CRYPTO_hash_context_finish ( + hash_context, + &coin_h->hash); } } |