/* This file is part of TALER 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 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/age_restriction.c * @brief Functions that are used for age restriction * @author Özgür Kesim */ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" #include #include 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, struct TALER_AgeCommitmentHash *ahash) { struct GNUNET_HashContext *hash_context; struct GNUNET_HashCode hash; GNUNET_assert (NULL != ahash); if (NULL == commitment) { memset (ahash, 0, sizeof(struct TALER_AgeCommitmentHash)); return; } GNUNET_assert (__builtin_popcount (commitment->mask.bits) - 1 == (int) commitment->num); hash_context = GNUNET_CRYPTO_hash_context_start (); for (size_t i = 0; i < commitment->num; i++) { GNUNET_CRYPTO_hash_context_read (hash_context, &commitment->keys[i], sizeof(commitment->keys[i])); } GNUNET_CRYPTO_hash_context_finish (hash_context, &hash); GNUNET_memcpy (&ahash->shash.bits, &hash.bits, sizeof(ahash->shash.bits)); } /* To a given age value between 0 and 31, returns the index of the age group * defined by the given mask. */ uint8_t get_age_group ( const struct TALER_AgeMask *mask, uint8_t age) { uint32_t m = mask->bits; uint8_t i = 0; while (m > 0) { if (0 >= age) break; m = m >> 1; i += m & 1; age--; } return i; } #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 *ncp) { struct GNUNET_HashCode seed_i; uint8_t num_pub; uint8_t num_priv; size_t i; GNUNET_assert (NULL != mask); GNUNET_assert (NULL != seed); 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; 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 we need and fill the rest of the * public keys with valid curve points. * We need to make sure that the public keys are proper points on the * elliptic curve, so we can't simply fill the struct with random values. */ for (i = 0; i < num_pub; i++) { struct TALER_AgeCommitmentPrivateKeyP key = {0}; struct TALER_AgeCommitmentPrivateKeyP *pkey = &key; /* Only save the private keys for age groups less than num_priv */ if (i < num_priv) 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 seed_i.bits[0] += 1; } return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_age_commitment_derive ( const struct TALER_AgeCommitmentProof *orig, const struct GNUNET_HashCode *salt, struct TALER_AgeCommitmentProof *newacp) { GNUNET_assert (NULL != newacp); GNUNET_assert (orig->proof.num <= orig->commitment.num); GNUNET_assert (((int) orig->commitment.num) == __builtin_popcount (orig->commitment.mask.bits) - 1); newacp->commitment.mask = orig->commitment.mask; newacp->commitment.num = orig->commitment.num; newacp->commitment.keys = GNUNET_new_array ( newacp->commitment.num, struct TALER_AgeCommitmentPublicKeyP); newacp->proof.num = orig->proof.num; newacp->proof.keys = NULL; if (0 != newacp->proof.num) newacp->proof.keys = GNUNET_new_array ( newacp->proof.num, struct TALER_AgeCommitmentPrivateKeyP); #ifndef AGE_RESTRICTION_WITH_ECDSA /* 1. Derive the public keys */ for (size_t i = 0; i < orig->commitment.num; i++) { GNUNET_CRYPTO_edx25519_public_key_derive ( &orig->commitment.keys[i].pub, salt, sizeof(*salt), &newacp->commitment.keys[i].pub); } /* 2. Derive the private keys */ for (size_t i = 0; i < orig->proof.num; i++) { GNUNET_CRYPTO_edx25519_private_key_derive ( &orig->proof.keys[i].priv, salt, sizeof(*salt), &newacp->proof.keys[i].priv); } #else { const char *label = GNUNET_h2s (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); } /* 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 return GNUNET_OK; } GNUNET_NETWORK_STRUCT_BEGIN /** * Age group mask in network byte order. */ struct TALER_AgeMaskNBO { uint32_t bits_nbo; }; /** * Used for attestation of a particular age */ struct TALER_AgeAttestationPS { /** * Purpose must be #TALER_SIGNATURE_WALLET_AGE_ATTESTATION. * (no GNUNET_PACKED here because the struct is already packed) */ struct GNUNET_CRYPTO_EccSignaturePurpose purpose; /** * Age mask that defines the underlying age groups */ struct TALER_AgeMaskNBO mask GNUNET_PACKED; /** * The particular age that this attestation is for. * We use uint32_t here for alignment. */ uint32_t age GNUNET_PACKED; }; GNUNET_NETWORK_STRUCT_END enum GNUNET_GenericReturnValue TALER_age_commitment_attest ( const struct TALER_AgeCommitmentProof *cp, uint8_t age, struct TALER_AgeAttestation *attest) { uint8_t group; GNUNET_assert (NULL != attest); GNUNET_assert (NULL != cp); group = get_age_group (&cp->commitment.mask, age); GNUNET_assert (group < 32); if (0 == group) { /* Age group 0 means: no attestation necessary. * We set the signature to zero and communicate success. */ memset (attest, 0, sizeof(struct TALER_AgeAttestation)); return GNUNET_OK; } if (group > cp->proof.num) return GNUNET_NO; { struct TALER_AgeAttestationPS at = { .purpose.size = htonl (sizeof(at)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION), .mask.bits_nbo = htonl (cp->commitment.mask.bits), .age = htonl (age), }; #ifndef AGE_RESTRICTION_WITH_ECDSA #define sign(a,b,c) GNUNET_CRYPTO_edx25519_sign (a,b,c) #else #define sign(a,b,c) GNUNET_CRYPTO_ecdsa_sign (a,b,c) #endif sign (&cp->proof.keys[group - 1].priv, &at, &attest->signature); } return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_age_commitment_verify ( const struct TALER_AgeCommitment *comm, uint8_t age, const struct TALER_AgeAttestation *attest) { uint8_t group; GNUNET_assert (NULL != attest); GNUNET_assert (NULL != comm); group = get_age_group (&comm->mask, age); GNUNET_assert (group < 32); /* Age group 0 means: no attestation necessary. */ if (0 == group) return GNUNET_OK; if (group > comm->num) { GNUNET_break_op (0); return GNUNET_NO; } { struct TALER_AgeAttestationPS at = { .purpose.size = htonl (sizeof(at)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_AGE_ATTESTATION), .mask.bits_nbo = htonl (comm->mask.bits), .age = htonl (age), }; #ifndef AGE_RESTRICTION_WITH_ECDSA #define verify(a,b,c,d) GNUNET_CRYPTO_edx25519_verify ((a),(b),(c),(d)) #else #define verify(a,b,c,d) GNUNET_CRYPTO_ecdsa_verify ((a),(b),(c),(d)) #endif return verify (TALER_SIGNATURE_WALLET_AGE_ATTESTATION, &at, &attest->signature, &comm->keys[group - 1].pub); } } void TALER_age_commitment_free ( struct TALER_AgeCommitment *commitment) { if (NULL == commitment) return; if (NULL != commitment->keys) { GNUNET_free (commitment->keys); commitment->keys = NULL; } GNUNET_free (commitment); } void TALER_age_proof_free ( struct TALER_AgeProof *proof) { if (NULL != proof->keys) { GNUNET_CRYPTO_zero_keys ( proof->keys, sizeof(*proof->keys) * proof->num); GNUNET_free (proof->keys); proof->keys = NULL; } GNUNET_free (proof); } void TALER_age_commitment_proof_free ( struct TALER_AgeCommitmentProof *cp) { if (NULL != cp->proof.keys) { GNUNET_CRYPTO_zero_keys ( cp->proof.keys, sizeof(*cp->proof.keys) * cp->proof.num); GNUNET_free (cp->proof.keys); cp->proof.keys = NULL; } if (NULL != cp->commitment.keys) { GNUNET_free (cp->commitment.keys); cp->commitment.keys = NULL; } } enum GNUNET_GenericReturnValue TALER_JSON_parse_age_groups (const json_t *root, struct TALER_AgeMask *mask) { enum GNUNET_GenericReturnValue ret; const char *str; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("age_groups", &str), GNUNET_JSON_spec_end () }; ret = GNUNET_JSON_parse (root, spec, NULL, NULL); if (GNUNET_OK == ret) TALER_parse_age_group_string (str, mask); GNUNET_JSON_parse_free (spec); return ret; } enum GNUNET_GenericReturnValue TALER_parse_age_group_string ( const char *groups, struct TALER_AgeMask *mask) { const char *pos = groups; unsigned int prev = 0; unsigned int val = 0; char c; while (*pos) { c = *pos++; if (':' == c) { if (prev >= val) return GNUNET_SYSERR; mask->bits |= 1 << val; prev = val; val = 0; continue; } if ('0'>c || '9'=val || 32<=val) return GNUNET_SYSERR; } if (32<=val || prev>=val) return GNUNET_SYSERR; mask->bits |= (1 << val); mask->bits |= 1; // mark zeroth group, too return GNUNET_OK; } char * TALER_age_mask_to_string ( const struct TALER_AgeMask *mask) { uint32_t bits = mask->bits; unsigned int n = 0; char *buf = GNUNET_malloc (32 * 3); // max characters possible char *pos = buf; if (NULL == buf) { return buf; } while (bits != 0) { bits >>= 1; n++; if (0 == (bits & 1)) { continue; } if (n > 9) { *(pos++) = '0' + n / 10; } *(pos++) = '0' + n % 10; if (0 != (bits >> 1)) { *(pos++) = ':'; } } return buf; } 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 */