[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).
This commit is contained in:
Özgür Kesim 2022-03-03 19:35:24 +01:00
parent 476ae53808
commit 4c53d42e44
Signed by: oec
GPG Key ID: 3D76A56D79EDD9D7
7 changed files with 235 additions and 163 deletions

1
.gitignore vendored
View File

@ -88,6 +88,7 @@ src/wire-plugins/test_wire_plugin
src/wire-plugins/test_wire_plugin_transactions_taler_bank src/wire-plugins/test_wire_plugin_transactions_taler_bank
src/pq/test_pq src/pq/test_pq
src/sq/test_sq src/sq/test_sq
src/util/test_age_restriction
src/util/test_amount src/util/test_amount
src/util/test_crypto src/util/test_crypto
src/util/test_json src/util/test_json

View File

@ -620,14 +620,14 @@ resolve_refreshes_reveal_denominations (struct MHD_Connection *connection,
oac = rctx->old_age_commitment; oac = rctx->old_age_commitment;
oac->mask = TEH_age_mask; oac->mask = TEH_age_mask;
oac->num = ng; 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 */ /* Extract old age commitment */
for (unsigned int i = 0; i< ng; i++) for (unsigned int i = 0; i< ng; i++)
{ {
struct GNUNET_JSON_Specification ac_spec[] = { struct GNUNET_JSON_Specification ac_spec[] = {
GNUNET_JSON_spec_fixed_auto (NULL, GNUNET_JSON_spec_fixed_auto (NULL,
&oac->pub[i]), &oac->keys[i]),
GNUNET_JSON_spec_end () GNUNET_JSON_spec_end ()
}; };

View File

@ -339,9 +339,9 @@ struct TALER_CoinSpendPrivateKeyP
struct TALER_AgeCommitmentPrivateKeyP 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 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 TALER_AgeAttestation
{ {
struct GNUNET_CRYPTO_EddsaSignature eddsa_signature; struct GNUNET_CRYPTO_EcdsaSignature signature;
}; };
extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash; extern const struct TALER_AgeCommitmentHash TALER_ZeroAgeCommitmentHash;
@ -3326,7 +3326,7 @@ struct TALER_AgeCommitment
* *
* The list has been allocated via GNUNET_malloc. * The list has been allocated via GNUNET_malloc.
*/ */
struct TALER_AgeCommitmentPublicKeyP *pub; struct TALER_AgeCommitmentPublicKeyP *keys;
}; };
struct TALER_AgeProof struct TALER_AgeProof
@ -3347,7 +3347,7 @@ struct TALER_AgeProof
* *
* The list has been allocated via GNUNET_malloc. * The list has been allocated via GNUNET_malloc.
*/ */
struct TALER_AgeCommitmentPrivateKeyP *priv; struct TALER_AgeCommitmentPrivateKeyP *keys;
}; };
struct TALER_AgeCommitmentProof struct TALER_AgeCommitmentProof

View File

@ -438,10 +438,11 @@ TALER_EXCHANGE_refreshes_reveal (
for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++) for (size_t i = 0; i < rd->melt_age_commitment_proof->commitment.num; i++)
{ {
GNUNET_assert (0 == GNUNET_assert (0 ==
json_array_append_new (old_age_commitment, json_array_append_new (
old_age_commitment,
GNUNET_JSON_from_data_auto ( GNUNET_JSON_from_data_auto (
&rd->melt_age_commitment_proof-> &rd->melt_age_commitment_proof->
commitment.pub[i]))); commitment.keys[i])));
} }
} }

View File

@ -116,6 +116,7 @@ libtalerutil_la_LDFLAGS = \
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
check_PROGRAMS = \ check_PROGRAMS = \
test_age_restriction \
test_amount \ test_amount \
test_crypto \ test_crypto \
test_helper_eddsa \ test_helper_eddsa \
@ -127,6 +128,11 @@ check_PROGRAMS = \
TESTS = \ TESTS = \
$(check_PROGRAMS) $(check_PROGRAMS)
test_age_restriction_SOURCES = \
test_age_restriction.c
test_age_restriction_LDADD = \
-lgnunetutil \
libtalerutil.la
test_amount_SOURCES = \ test_amount_SOURCES = \
test_amount.c test_amount.c

View File

@ -46,9 +46,9 @@ TALER_age_commitment_hash (
for (size_t i = 0; i < commitment->num; i++) for (size_t i = 0; i < commitment->num; i++)
{ {
GNUNET_CRYPTO_hash_context_read (hash_context, GNUNET_CRYPTO_hash_context_read (hash_context,
&commitment->pub[i], &commitment->keys[i],
sizeof(struct sizeof(struct
GNUNET_CRYPTO_EddsaPublicKey)); GNUNET_CRYPTO_EcdsaPublicKey));
} }
GNUNET_CRYPTO_hash_context_finish (hash_context, 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 /* To a given age value between 0 and 31, returns the index of the age group
* defined by the given mask. * defined by the given mask.
*/ */
static uint8_t uint8_t
get_age_group ( get_age_group (
const struct TALER_AgeMask *mask, const struct TALER_AgeMask *mask,
uint8_t age) uint8_t age)
@ -102,14 +102,14 @@ TALER_age_restriction_commit (
new->commitment.mask.bits = mask->bits; new->commitment.mask.bits = mask->bits;
new->commitment.num = num_pub; new->commitment.num = num_pub;
new->proof.num = num_priv; 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, num_pub,
struct TALER_AgeCommitmentPublicKeyP); struct TALER_AgeCommitmentPublicKeyP);
if (0 < num_priv) if (0 < num_priv)
new->proof.priv = GNUNET_new_array ( new->proof.keys = GNUNET_new_array (
num_priv, num_priv,
struct TALER_AgeCommitmentPrivateKeyP); struct TALER_AgeCommitmentPrivateKeyP);
@ -119,35 +119,40 @@ TALER_age_restriction_commit (
* elliptic curve, so we can't simply fill the struct with random values. */ * elliptic curve, so we can't simply fill the struct with random values. */
for (i = 0; i < num_pub; i++) 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 key = {0};
struct TALER_AgeCommitmentPrivateKeyP *priv = &key; struct TALER_AgeCommitmentPrivateKeyP *pkey = &key;
/* Only save the private keys for age groups less than num_priv */ /* Only save the private keys for age groups less than num_priv */
if (i < num_priv) if (i < num_priv)
priv = &new->proof.priv[i]; pkey = &new->proof.keys[i];
if (GNUNET_OK != if (GNUNET_OK !=
GNUNET_CRYPTO_kdf (priv, GNUNET_CRYPTO_kdf (pkey,
sizeof (*priv), sizeof (*pkey),
&saltBE, &salti,
sizeof (saltBE), sizeof (salti),
"taler-age-commitment-derivation", "age commitment",
strlen ( strlen ("age derivation"),
"taler-age-commitment-derivation"),
NULL, 0)) NULL, 0))
goto FAIL; goto FAIL;
GNUNET_CRYPTO_eddsa_key_get_public (&priv->eddsa_priv, /* See GNUNET_CRYPTO_ecdsa_key_create */
&new->commitment.pub[i].eddsa_pub); 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; return GNUNET_OK;
FAIL: FAIL:
GNUNET_free (new->commitment.pub); GNUNET_free (new->commitment.keys);
if (NULL != new->proof.priv) if (NULL != new->proof.keys)
GNUNET_free (new->proof.priv); GNUNET_free (new->proof.keys);
return GNUNET_SYSERR; return GNUNET_SYSERR;
} }
@ -158,128 +163,52 @@ TALER_age_commitment_derive (
const uint64_t salt, const uint64_t salt,
struct TALER_AgeCommitmentProof *new) struct TALER_AgeCommitmentProof *new)
{ {
struct GNUNET_CRYPTO_EccScalar scalar; char label[sizeof(uint64_t) + 1] = {0};
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];
* */
GNUNET_assert (NULL != new); GNUNET_assert (NULL != new);
GNUNET_assert (orig->commitment.num== __builtin_popcount ( GNUNET_assert (orig->proof.num <=
orig->commitment.mask.bits) - 1); 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.mask = orig->commitment.mask;
new->commitment.num = orig->commitment.num; new->commitment.num = orig->commitment.num;
new->proof.num = orig->proof.num; new->commitment.keys = GNUNET_new_array (
new->commitment.pub = GNUNET_new_array (
new->commitment.num, new->commitment.num,
struct TALER_AgeCommitmentPublicKeyP); struct TALER_AgeCommitmentPublicKeyP);
new->proof.priv = GNUNET_new_array (
new->proof.num = orig->proof.num;
new->proof.keys = NULL;
if (0 != new->proof.num)
new->proof.keys = GNUNET_new_array (
new->proof.num, new->proof.num,
struct TALER_AgeCommitmentPrivateKeyP); struct TALER_AgeCommitmentPrivateKeyP);
/* scalar multiply the public keys on the curve */ memcpy (label, &salt, sizeof(salt));
/* 1. Derive the public keys */
for (size_t i = 0; i < orig->commitment.num; i++) for (size_t i = 0; i < orig->commitment.num; i++)
{ {
/* We shift all keys by the same scalar */ GNUNET_CRYPTO_ecdsa_public_key_derive (
struct GNUNET_CRYPTO_EccPoint *p = (struct &orig->commitment.keys[i].pub,
GNUNET_CRYPTO_EccPoint *) &orig-> label,
commitment.pub[i]; "age commitment derive",
struct GNUNET_CRYPTO_EccPoint *np = (struct &new->commitment.keys[i].pub);
GNUNET_CRYPTO_EccPoint *) &new->
commitment.pub[i];
if (GNUNET_OK !=
GNUNET_CRYPTO_ecc_pmul_mpi (
p,
&scalar,
np))
goto FAIL;
} }
/* multiply the private keys */ /* 2. Derive the private keys */
/* we borough ideas from GNUNET_CRYPTO_ecdsa_private_key_derive */
{
for (size_t i = 0; i < orig->proof.num; i++) for (size_t i = 0; i < orig->proof.num; i++)
{ {
uint8_t dc[32]; struct GNUNET_CRYPTO_EcdsaPrivateKey *priv;
gcry_mpi_t f, x, d, n; priv = GNUNET_CRYPTO_ecdsa_private_key_derive (
gcry_ctx_t ctx; &orig->proof.keys[i].priv,
label,
GNUNET_assert (0==gcry_mpi_ec_new (&ctx, NULL, "Ed25519")); "age commitment derive");
n = gcry_mpi_ec_get_mpi ("n", ctx, 1); new->proof.keys[i].priv = *priv;
GNUNET_free (priv);
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 */
}
} }
return GNUNET_OK; 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; return GNUNET_OK;
} }
if (group >= cp->proof.num) if (group > cp->proof.num)
return GNUNET_NO; return GNUNET_NO;
{ {
@ -320,9 +249,9 @@ TALER_age_commitment_attest (
.age = age .age = age
}; };
GNUNET_CRYPTO_eddsa_sign (&cp->proof.priv[group - 1].eddsa_priv, GNUNET_CRYPTO_ecdsa_sign (&cp->proof.keys[group - 1].priv,
&at, &at,
&attest->eddsa_signature); &attest->signature);
} }
return GNUNET_OK; return GNUNET_OK;
@ -349,7 +278,7 @@ TALER_age_commitment_verify (
if (0 == group) if (0 == group)
return GNUNET_OK; return GNUNET_OK;
if (group >= comm->num) if (group > comm->num)
return GNUNET_NO; return GNUNET_NO;
{ {
@ -361,10 +290,10 @@ TALER_age_commitment_verify (
}; };
return return
GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION, GNUNET_CRYPTO_ecdsa_verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION,
&at, &at,
&attest->eddsa_signature, &attest->signature,
&comm->pub[group - 1].eddsa_pub); &comm->keys[group - 1].pub);
} }
} }
@ -376,10 +305,10 @@ TALER_age_commitment_free (
if (NULL == commitment) if (NULL == commitment)
return; return;
if (NULL != commitment->pub) if (NULL != commitment->keys)
{ {
GNUNET_free (commitment->pub); GNUNET_free (commitment->keys);
commitment->pub = NULL; commitment->keys = NULL;
} }
GNUNET_free (commitment); GNUNET_free (commitment);
} }
@ -389,14 +318,14 @@ void
TALER_age_proof_free ( TALER_age_proof_free (
struct TALER_AgeProof *proof) struct TALER_AgeProof *proof)
{ {
if (NULL != proof->priv) if (NULL != proof->keys)
{ {
GNUNET_CRYPTO_zero_keys ( GNUNET_CRYPTO_zero_keys (
proof->priv, proof->keys,
sizeof(*proof->priv) * proof->num); sizeof(*proof->keys) * proof->num);
GNUNET_free (proof->priv); GNUNET_free (proof->keys);
proof->priv = NULL; proof->keys = NULL;
} }
GNUNET_free (proof); GNUNET_free (proof);
} }
@ -406,19 +335,19 @@ void
TALER_age_commitment_proof_free ( TALER_age_commitment_proof_free (
struct TALER_AgeCommitmentProof *cp) struct TALER_AgeCommitmentProof *cp)
{ {
if (NULL != cp->proof.priv) if (NULL != cp->proof.keys)
{ {
GNUNET_CRYPTO_zero_keys ( GNUNET_CRYPTO_zero_keys (
cp->proof.priv, cp->proof.keys,
sizeof(*cp->proof.priv) * cp->proof.num); sizeof(*cp->proof.keys) * cp->proof.num);
GNUNET_free (cp->proof.priv); GNUNET_free (cp->proof.keys);
cp->proof.priv = NULL; cp->proof.keys = NULL;
} }
if (NULL != cp->commitment.pub) if (NULL != cp->commitment.keys)
{ {
GNUNET_free (cp->commitment.pub); GNUNET_free (cp->commitment.keys);
cp->commitment.pub = NULL; cp->commitment.keys = NULL;
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/>
*/
/**
* @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 */