From 4c53d42e44cbab55194b2f0fb71ffc3cb082b56a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Thu, 3 Mar 2022 19:35:24 +0100 Subject: [PATCH] [age restriction] progress 18/n - attestation tested - Unit-tests for commit, derive, attest and verify added, with multiple combinations of minimum age and commited age. - Fixed crypto implementation (eddsa -> ecdsa) - Using now standard functionality from GNUNET: GNUNET_CRYPTO_ecdsa_{private,public}_key_derive All tests pass (unit tests in util/ and 'make check' in testing). --- .gitignore | 1 + .../taler-exchange-httpd_refreshes_reveal.c | 4 +- src/include/taler_crypto_lib.h | 14 +- src/lib/exchange_api_refreshes_reveal.c | 9 +- src/util/Makefile.am | 6 + src/util/age_restriction.c | 229 ++++++------------ src/util/test_age_restriction.c | 135 +++++++++++ 7 files changed, 235 insertions(+), 163 deletions(-) create mode 100644 src/util/test_age_restriction.c diff --git a/.gitignore b/.gitignore index 9fbf5b058..ace3682e2 100644 --- a/.gitignore +++ b/.gitignore @@ -88,6 +88,7 @@ src/wire-plugins/test_wire_plugin src/wire-plugins/test_wire_plugin_transactions_taler_bank src/pq/test_pq src/sq/test_sq +src/util/test_age_restriction src/util/test_amount src/util/test_crypto src/util/test_json diff --git a/src/exchange/taler-exchange-httpd_refreshes_reveal.c b/src/exchange/taler-exchange-httpd_refreshes_reveal.c index caba557c5..d5cb2e476 100644 --- a/src/exchange/taler-exchange-httpd_refreshes_reveal.c +++ b/src/exchange/taler-exchange-httpd_refreshes_reveal.c @@ -620,14 +620,14 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection, oac = rctx->old_age_commitment; oac->mask = TEH_age_mask; oac->num = ng; - oac->pub = GNUNET_new_array (ng, struct TALER_AgeCommitmentPublicKeyP); + oac->keys = GNUNET_new_array (ng, struct TALER_AgeCommitmentPublicKeyP); /* Extract old age commitment */ for (unsigned int i = 0; i< ng; i++) { struct GNUNET_JSON_Specification ac_spec[] = { GNUNET_JSON_spec_fixed_auto (NULL, - &oac->pub[i]), + &oac->keys[i]), GNUNET_JSON_spec_end () }; diff --git a/src/include/taler_crypto_lib.h b/src/include/taler_crypto_lib.h index ea1a73af5..e114ef83a 100644 --- a/src/include/taler_crypto_lib.h +++ b/src/include/taler_crypto_lib.h @@ -339,9 +339,9 @@ struct TALER_CoinSpendPrivateKeyP struct TALER_AgeCommitmentPrivateKeyP { /** - * Taler uses EdDSA for coins when signing age verification attestation. + * Taler uses EcDSA for coins when signing age verification attestation. */ - struct GNUNET_CRYPTO_EddsaPrivateKey eddsa_priv; + struct GNUNET_CRYPTO_EcdsaPrivateKey priv; }; @@ -351,9 +351,9 @@ struct TALER_AgeCommitmentPrivateKeyP struct TALER_AgeCommitmentPublicKeyP { /** - * Taler uses EdDSA for coins when signing age verification attestation. + * Taler uses EcDSA for coins when signing age verification attestation. */ - struct GNUNET_CRYPTO_EddsaPublicKey eddsa_pub; + struct GNUNET_CRYPTO_EcdsaPublicKey pub; }; @@ -869,7 +869,7 @@ struct TALER_AgeCommitmentHash */ struct TALER_AgeAttestation { - struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; + struct GNUNET_CRYPTO_EcdsaSignature signature; }; extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash; @@ -3326,7 +3326,7 @@ struct TALER_AgeCommitment * * The list has been allocated via GNUNET_malloc. */ - struct TALER_AgeCommitmentPublicKeyP *pub; + struct TALER_AgeCommitmentPublicKeyP *keys; }; struct TALER_AgeProof @@ -3347,7 +3347,7 @@ struct TALER_AgeProof * * The list has been allocated via GNUNET_malloc. */ - struct TALER_AgeCommitmentPrivateKeyP *priv; + struct TALER_AgeCommitmentPrivateKeyP *keys; }; struct TALER_AgeCommitmentProof diff --git a/src/lib/exchange_api_refreshes_reveal.c b/src/lib/exchange_api_refreshes_reveal.c index 6427c637b..cd2a1d1f4 100644 --- a/src/lib/exchange_api_refreshes_reveal.c +++ b/src/lib/exchange_api_refreshes_reveal.c @@ -438,10 +438,11 @@ TALER_EXCHANGE_refreshes_reveal ( for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++) { GNUNET_assert (0 == - json_array_append_new (old_age_commitment, - GNUNET_JSON_from_data_auto ( - &rd->melt_age_commitment_proof-> - commitment.pub[i]))); + json_array_append_new ( + old_age_commitment, + GNUNET_JSON_from_data_auto ( + &rd->melt_age_commitment_proof-> + commitment.keys[i]))); } } diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 022d0611c..a24f081b5 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -116,6 +116,7 @@ libtalerutil_la_LDFLAGS = \ AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; check_PROGRAMS = \ + test_age_restriction \ test_amount \ test_crypto \ test_helper_eddsa \ @@ -127,6 +128,11 @@ check_PROGRAMS = \ TESTS = \ $(check_PROGRAMS) +test_age_restriction_SOURCES = \ + test_age_restriction.c +test_age_restriction_LDADD = \ + -lgnunetutil \ + libtalerutil.la test_amount_SOURCES = \ test_amount.c diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c index 8e088a408..58db23413 100644 --- a/src/util/age_restriction.c +++ b/src/util/age_restriction.c @@ -46,9 +46,9 @@ TALER_age_commitment_hash ( for (size_t i = 0; i < commitment->num; i++) { GNUNET_CRYPTO_hash_context_read (hash_context, - &commitment->pub[i], + &commitment->keys[i], sizeof(struct - GNUNET_CRYPTO_EddsaPublicKey)); + GNUNET_CRYPTO_EcdsaPublicKey)); } GNUNET_CRYPTO_hash_context_finish (hash_context, @@ -62,7 +62,7 @@ TALER_age_commitment_hash ( /* To a given age value between 0 and 31, returns the index of the age group * defined by the given mask. */ -static uint8_t +uint8_t get_age_group ( const struct TALER_AgeMask *mask, uint8_t age) @@ -102,14 +102,14 @@ TALER_age_restriction_commit ( new->commitment.mask.bits = mask->bits; new->commitment.num = num_pub; new->proof.num = num_priv; - new->proof.priv = NULL; + new->proof.keys = NULL; - new->commitment.pub = GNUNET_new_array ( + new->commitment.keys = GNUNET_new_array ( num_pub, struct TALER_AgeCommitmentPublicKeyP); if (0 < num_priv) - new->proof.priv = GNUNET_new_array ( + new->proof.keys = GNUNET_new_array ( num_priv, struct TALER_AgeCommitmentPrivateKeyP); @@ -119,35 +119,40 @@ TALER_age_restriction_commit ( * elliptic curve, so we can't simply fill the struct with random values. */ for (i = 0; i < num_pub; i++) { - uint64_t saltBE = htonl (salt + i); + uint64_t salti = salt + i; struct TALER_AgeCommitmentPrivateKeyP key = {0}; - struct TALER_AgeCommitmentPrivateKeyP *priv = &key; + struct TALER_AgeCommitmentPrivateKeyP *pkey = &key; + /* Only save the private keys for age groups less than num_priv */ if (i < num_priv) - priv = &new->proof.priv[i]; + pkey = &new->proof.keys[i]; if (GNUNET_OK != - GNUNET_CRYPTO_kdf (priv, - sizeof (*priv), - &saltBE, - sizeof (saltBE), - "taler-age-commitment-derivation", - strlen ( - "taler-age-commitment-derivation"), + GNUNET_CRYPTO_kdf (pkey, + sizeof (*pkey), + &salti, + sizeof (salti), + "age commitment", + strlen ("age derivation"), NULL, 0)) goto FAIL; - GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv, - &new->commitment.pub[i].eddsa_pub); + /* See GNUNET_CRYPTO_ecdsa_key_create */ + pkey->priv.d[0] &= 248; + pkey->priv.d[31] &= 127; + pkey->priv.d[31] |= 64; + + GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, + &new->commitment.keys[i].pub); } return GNUNET_OK; FAIL: - GNUNET_free (new->commitment.pub); - if (NULL != new->proof.priv) - GNUNET_free (new->proof.priv); + GNUNET_free (new->commitment.keys); + if (NULL != new->proof.keys) + GNUNET_free (new->proof.keys); return GNUNET_SYSERR; } @@ -158,128 +163,52 @@ TALER_age_commitment_derive ( const uint64_t salt, struct TALER_AgeCommitmentProof *new) { - struct GNUNET_CRYPTO_EccScalar scalar; - uint64_t saltBT = htonl (salt); - int64_t factor; - - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_kdf ( - &factor, - sizeof (factor), - &saltBT, - sizeof (saltBT), - "taler-age-restriction-derivation", - strlen ("taler-age-restriction-derivation"), - NULL, 0)); - - GNUNET_CRYPTO_ecc_scalar_from_int (factor, &scalar); - - /* - * age commitment consists of GNUNET_CRYPTO_Eddsa{Private,Public}Key - * - * GNUNET_CRYPTO_EddsaPrivateKey is a - * unsigned char d[256 / 8]; - * - * GNUNET_CRYPTO_EddsaPublicKey is a - * unsigned char q_y[256 / 8]; - * - * We want to multiply, both, the Private Key by an integer factor and the - * public key (point on curve) with the equivalent scalar. - * - * From the salt we will derive - * 1. a scalar to multiply the public keys with - * 2. a factor to multiply the private key with - * - * Invariants: - * point*scalar == public(private*factor) - * - * A point on a curve is GNUNET_CRYPTO_EccPoint which is - * unsigned char v[256 / 8]; - * - * A ECC scalar for use in point multiplications is a - * GNUNET_CRYPTO_EccScalar which is a - * unsigned char v[256 / 8]; - * */ + char label[sizeof(uint64_t) + 1] = {0}; GNUNET_assert (NULL != new); - GNUNET_assert (orig->commitment.num== __builtin_popcount ( - orig->commitment.mask.bits) - 1); - GNUNET_assert (orig->proof.num <= orig->commitment.num); + GNUNET_assert (orig->proof.num <= + orig->commitment.num); + GNUNET_assert (orig->commitment.num == + __builtin_popcount (orig->commitment.mask.bits) - 1); new->commitment.mask = orig->commitment.mask; new->commitment.num = orig->commitment.num; - new->proof.num = orig->proof.num; - new->commitment.pub = GNUNET_new_array ( + new->commitment.keys = GNUNET_new_array ( new->commitment.num, struct TALER_AgeCommitmentPublicKeyP); - new->proof.priv = GNUNET_new_array ( - new->proof.num, - struct TALER_AgeCommitmentPrivateKeyP); - /* scalar multiply the public keys on the curve */ + new->proof.num = orig->proof.num; + new->proof.keys = NULL; + if (0 != new->proof.num) + new->proof.keys = GNUNET_new_array ( + new->proof.num, + struct TALER_AgeCommitmentPrivateKeyP); + + memcpy (label, &salt, sizeof(salt)); + + /* 1. Derive the public keys */ for (size_t i = 0; i < orig->commitment.num; i++) { - /* We shift all keys by the same scalar */ - struct GNUNET_CRYPTO_EccPoint *p = (struct - GNUNET_CRYPTO_EccPoint *) &orig-> - commitment.pub[i]; - struct GNUNET_CRYPTO_EccPoint *np = (struct - GNUNET_CRYPTO_EccPoint *) &new-> - commitment.pub[i]; - if (GNUNET_OK != - GNUNET_CRYPTO_ecc_pmul_mpi ( - p, - &scalar, - np)) - goto FAIL; - + GNUNET_CRYPTO_ecdsa_public_key_derive ( + &orig->commitment.keys[i].pub, + label, + "age commitment derive", + &new->commitment.keys[i].pub); } - /* multiply the private keys */ - /* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */ + /* 2. Derive the private keys */ + for (size_t i = 0; i < orig->proof.num; i++) { - for (size_t i = 0; i < orig->proof.num; i++) - { - uint8_t dc[32]; - gcry_mpi_t f, x, d, n; - gcry_ctx_t ctx; - - GNUNET_assert (0==gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); - n = gcry_mpi_ec_get_mpi ("n", ctx, 1); - - GNUNET_CRYPTO_mpi_scan_unsigned (&f, (unsigned char*) &factor, - sizeof(factor)); - - for (size_t j = 0; j < 32; j++) - dc[i] = orig->proof.priv[i].eddsa_priv.d[31 - j]; - GNUNET_CRYPTO_mpi_scan_unsigned (&x, dc, sizeof(dc)); - - d = gcry_mpi_new (256); - gcry_mpi_mulm (d, f, x, n); - GNUNET_CRYPTO_mpi_print_unsigned (dc, sizeof(dc), d); - - for (size_t j = 0; j <32; j++) - new->proof.priv[i].eddsa_priv.d[j] = dc[31 - 1]; - - sodium_memzero (dc, sizeof(dc)); - gcry_mpi_release (d); - gcry_mpi_release (x); - gcry_mpi_release (n); - gcry_mpi_release (f); - gcry_ctx_release (ctx); - - /* TODO: add test to make sure that the calculated private key generate - * the same public keys */ - } - + struct GNUNET_CRYPTO_EcdsaPrivateKey *priv; + priv = GNUNET_CRYPTO_ecdsa_private_key_derive ( + &orig->proof.keys[i].priv, + label, + "age commitment derive"); + new->proof.keys[i].priv = *priv; + GNUNET_free (priv); } return GNUNET_OK; - -FAIL: - GNUNET_free (new->commitment.pub); - GNUNET_free (new->proof.priv); - return GNUNET_SYSERR; } @@ -309,7 +238,7 @@ TALER_age_commitment_attest ( return GNUNET_OK; } - if (group >= cp->proof.num) + if (group > cp->proof.num) return GNUNET_NO; { @@ -320,9 +249,9 @@ TALER_age_commitment_attest ( .age = age }; - GNUNET_CRYPTO_eddsa_sign (&cp->proof.priv[group - 1].eddsa_priv, + GNUNET_CRYPTO_ecdsa_sign (&cp->proof.keys[group - 1].priv, &at, - &attest->eddsa_signature); + &attest->signature); } return GNUNET_OK; @@ -349,7 +278,7 @@ TALER_age_commitment_verify ( if (0 == group) return GNUNET_OK; - if (group >= comm->num) + if (group > comm->num) return GNUNET_NO; { @@ -361,10 +290,10 @@ TALER_age_commitment_verify ( }; return - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION, + GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION, &at, - &attest->eddsa_signature, - &comm->pub[group - 1].eddsa_pub); + &attest->signature, + &comm->keys[group - 1].pub); } } @@ -376,10 +305,10 @@ TALER_age_commitment_free ( if (NULL == commitment) return; - if (NULL != commitment->pub) + if (NULL != commitment->keys) { - GNUNET_free (commitment->pub); - commitment->pub = NULL; + GNUNET_free (commitment->keys); + commitment->keys = NULL; } GNUNET_free (commitment); } @@ -389,14 +318,14 @@ void TALER_age_proof_free ( struct TALER_AgeProof *proof) { - if (NULL != proof->priv) + if (NULL != proof->keys) { GNUNET_CRYPTO_zero_keys ( - proof->priv, - sizeof(*proof->priv) * proof->num); + proof->keys, + sizeof(*proof->keys) * proof->num); - GNUNET_free (proof->priv); - proof->priv = NULL; + GNUNET_free (proof->keys); + proof->keys = NULL; } GNUNET_free (proof); } @@ -406,19 +335,19 @@ void TALER_age_commitment_proof_free ( struct TALER_AgeCommitmentProof *cp) { - if (NULL != cp->proof.priv) + if (NULL != cp->proof.keys) { GNUNET_CRYPTO_zero_keys ( - cp->proof.priv, - sizeof(*cp->proof.priv) * cp->proof.num); + cp->proof.keys, + sizeof(*cp->proof.keys) * cp->proof.num); - GNUNET_free (cp->proof.priv); - cp->proof.priv = NULL; + GNUNET_free (cp->proof.keys); + cp->proof.keys = NULL; } - if (NULL != cp->commitment.pub) + if (NULL != cp->commitment.keys) { - GNUNET_free (cp->commitment.pub); - cp->commitment.pub = NULL; + GNUNET_free (cp->commitment.keys); + cp->commitment.keys = NULL; } } diff --git a/src/util/test_age_restriction.c b/src/util/test_age_restriction.c new file mode 100644 index 000000000..a047714f7 --- /dev/null +++ b/src/util/test_age_restriction.c @@ -0,0 +1,135 @@ +/* + This file is part of TALER + (C) 2022 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 +*/ + +/** + * @file util/test_age_restriction.c + * @brief Tests for age restriction specific logic + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_crypto_lib.h" + +static struct TALER_AgeMask age_mask = { + .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21 +}; + +extern uint8_t +get_age_group ( + const struct TALER_AgeMask *mask, + uint8_t age); + +enum GNUNET_GenericReturnValue +test_attestation (void) +{ + uint8_t age; + for (age = 0; age < 35; age++) + { + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeCommitmentProof acp[3] = {0}; + struct TALER_AgeAttestation at = {0}; + uint8_t age_group = get_age_group (&age_mask, age); + uint64_t salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + + ret = TALER_age_restriction_commit (&age_mask, + age, + salt, + &acp[0]); + + printf ( + "commit(age:%d) == %d; proof.num: %ld; age_group: %d\n", + age, + ret, + acp[0].proof.num, + age_group); + + for (uint8_t i = 0; i<2; i++) + { + /* Also derive another commitment right away */ + salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT64_MAX); + GNUNET_assert (GNUNET_OK == + TALER_age_commitment_derive (&acp[i], + salt, + &acp[i + 1])); + } + + for (uint8_t i = 0; i < 3; i++) + { + for (uint8_t min = 0; min < 22; min++) + { + uint8_t min_group = get_age_group (&age_mask, min); + + ret = TALER_age_commitment_attest (&acp[i], + min, + &at); + + printf ( + "[%s]: attest(min:%d, age:%d) == %d; age_group: %d, min_group: %d\n", + i == 0 ? "commit" : "derive", + min, + age, + ret, + age_group, + min_group); + + if (min_group <= age_group && + GNUNET_OK != ret) + return GNUNET_SYSERR; + + if (min_group > age_group && + GNUNET_NO != ret) + return GNUNET_SYSERR; + + if (min_group > age_group) + continue; + + ret = TALER_age_commitment_verify (&acp[i].commitment, + min, + &at); + + printf ( + "[%s]: verify(min:%d, age:%d) == %d; age_group:%d, min_group: %d\n", + i == 0 ? "commit" : "derive", + min, + age, + ret, + age_group, + min_group); + + if (GNUNET_OK != ret) + return ret; + } + } + } + return GNUNET_OK; +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + if (GNUNET_OK != test_attestation ()) + return 1; + return 0; +} + + +/* end of test_age_restriction.c */