diff options
Diffstat (limited to 'src/util')
57 files changed, 27711 insertions, 1765 deletions
diff --git a/src/util/.gitignore b/src/util/.gitignore index 946924dcc..d79786ec7 100644 --- a/src/util/.gitignore +++ b/src/util/.gitignore @@ -1,9 +1,12 @@ taler-config test_payto -taler-crypto-worker taler-exchange-secmod-rsa +taler-exchange-secmod-cs taler-exchange-secmod-eddsa test_helper_rsa test_helper_rsa_home/ +test_helper_cs +test_helper_cs_home/ test_helper_eddsa test_helper_eddsa_home/ +test_conversion diff --git a/src/util/Makefile.am b/src/util/Makefile.am index 790bba735..d2504588b 100644 --- a/src/util/Makefile.am +++ b/src/util/Makefile.am @@ -10,19 +10,24 @@ endif pkgcfgdir = $(prefix)/share/taler/config.d/ pkgcfg_DATA = \ + currencies.conf \ paths.conf \ taler-exchange-secmod-eddsa.conf \ - taler-exchange-secmod-rsa.conf + taler-exchange-secmod-rsa.conf \ + taler-exchange-secmod-cs.conf EXTRA_DIST = \ $(pkgcfg_DATA) \ taler-config.in \ test_helper_eddsa.conf \ - test_helper_rsa.conf + test_helper_rsa.conf \ + test_helper_cs.conf \ + test_conversion.sh bin_PROGRAMS = \ taler-exchange-secmod-eddsa \ - taler-exchange-secmod-rsa + taler-exchange-secmod-rsa \ + taler-exchange-secmod-cs bin_SCRIPTS = \ taler-config @@ -48,46 +53,50 @@ taler_exchange_secmod_rsa_LDADD = \ $(LIBGCRYPT_LIBS) \ $(XLIB) -taler_exchange_secmod_eddsa_SOURCES = \ - taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \ +taler_exchange_secmod_cs_SOURCES = \ + taler-exchange-secmod-cs.c taler-exchange-secmod-cs.h \ secmod_common.c secmod_common.h -taler_exchange_secmod_eddsa_LDADD = \ +taler_exchange_secmod_cs_LDADD = \ libtalerutil.la \ -lgnunetutil \ -lpthread \ $(LIBGCRYPT_LIBS) \ $(XLIB) -taler_crypto_worker_SOURCES = \ - taler-crypto-worker.c -taler_crypto_worker_LDADD = \ +taler_exchange_secmod_eddsa_SOURCES = \ + taler-exchange-secmod-eddsa.c taler-exchange-secmod-eddsa.h \ + secmod_common.c secmod_common.h +taler_exchange_secmod_eddsa_LDADD = \ libtalerutil.la \ -lgnunetutil \ - -lgnunetjson \ - -ljansson \ -lpthread \ $(LIBGCRYPT_LIBS) \ $(XLIB) - lib_LTLIBRARIES = \ libtalerutil.la libtalerutil_la_SOURCES = \ + age_restriction.c \ amount.c \ + aml_signatures.c \ auditor_signatures.c \ config.c \ + conversion.c \ crypto.c \ + crypto_confirmation.c \ + crypto_contract.c \ crypto_helper_common.c crypto_helper_common.h \ crypto_helper_rsa.c \ + crypto_helper_cs.c \ crypto_helper_esign.c \ crypto_wire.c \ denom.c \ exchange_signatures.c \ - extension_age_restriction.c \ getopt.c \ lang.c \ iban.c \ + merchant_signatures.c \ mhd.c \ offline_signatures.c \ payto.c \ @@ -101,28 +110,49 @@ libtalerutil_la_SOURCES = \ libtalerutil_la_LIBADD = \ -lgnunetutil \ + -lgnunetjson \ + -lsodium \ + -ljansson \ $(LIBGCRYPT_LIBS) \ -lmicrohttpd $(XLIB) \ + -lunistring \ + -lz \ -lm libtalerutil_la_LDFLAGS = \ - -version-info 0:0:0 \ + -version-info 3:3:2 \ -no-undefined AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH; check_PROGRAMS = \ + test_age_restriction \ test_amount \ + test_conversion \ test_crypto \ test_helper_eddsa \ test_helper_rsa \ + test_helper_cs \ test_payto \ test_url TESTS = \ $(check_PROGRAMS) +test_age_restriction_SOURCES = \ + test_age_restriction.c +test_age_restriction_LDADD = \ + -lgnunetutil \ + libtalerutil.la + +test_conversion_SOURCES = \ + test_conversion.c +test_conversion_LDADD = \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + libtalerutil.la test_amount_SOURCES = \ test_amount.c @@ -133,8 +163,9 @@ test_amount_LDADD = \ test_crypto_SOURCES = \ test_crypto.c test_crypto_LDADD = \ + libtalerutil.la \ -lgnunetutil \ - libtalerutil.la + -ljansson test_payto_SOURCES = \ test_payto.c @@ -154,6 +185,12 @@ test_helper_rsa_LDADD = \ -lgnunetutil \ libtalerutil.la +test_helper_cs_SOURCES = \ + test_helper_cs.c +test_helper_cs_LDADD = \ + -lgnunetutil \ + libtalerutil.la + test_url_SOURCES = \ test_url.c test_url_LDADD = \ diff --git a/src/util/age_restriction.c b/src/util/age_restriction.c new file mode 100644 index 000000000..c2a7fc07c --- /dev/null +++ b/src/util/age_restriction.c @@ -0,0 +1,795 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @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 <gnunet/gnunet_json_lib.h> +#include <gcrypt.h> +#include <stdint.h> + +struct +#ifndef AGE_RESTRICTION_WITH_ECDSA +GNUNET_CRYPTO_Edx25519PublicKey +#else +GNUNET_CRYPTO_EcdsaPublicKey +#endif +TALER_age_commitment_base_public_key = { + .q_y = { 0x64, 0x41, 0xb9, 0xbd, 0xbf, 0x14, 0x39, 0x8e, + 0x46, 0xeb, 0x5c, 0x1d, 0x34, 0xd3, 0x9b, 0x2f, + 0x9b, 0x7d, 0xc8, 0x18, 0xeb, 0x9c, 0x09, 0xfb, + 0x43, 0xad, 0x16, 0x64, 0xbc, 0x18, 0x49, 0xb5}, +}; + +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 +TALER_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; +} + + +uint8_t +TALER_get_lowest_age ( + const struct TALER_AgeMask *mask, + uint8_t age) +{ + uint32_t m = mask->bits; + uint8_t group = TALER_get_age_group (mask, age); + uint8_t lowest = 0; + + while (group > 0) + { + m = m >> 1; + if (m & 1) + group--; + lowest++; + } + + return lowest; +} + + +#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 + */ +static void +ecdsa_create_from_seed ( + const void *seed, + size_t seed_size, + struct GNUNET_CRYPTO_EcdsaPrivateKey *key) +{ + enum GNUNET_GenericReturnValue ret; + + GNUNET_assert ( + GNUNET_OK == + GNUNET_CRYPTO_kdf (key, + sizeof (*key), + &seed, + seed_size, + "age commitment", + sizeof ("age commitment") - 1, + NULL, 0)); + /* See GNUNET_CRYPTO_ecdsa_key_create */ + key->d[0] &= 248; + key->d[31] &= 127; + key->d[31] |= 64; +} + + +#endif + + +void +TALER_age_restriction_commit ( + const struct TALER_AgeMask *mask, + 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); /* first bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = TALER_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 + ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); + GNUNET_CRYPTO_ecdsa_key_get_public (&pkey->priv, + &ncp->commitment.keys[i].pub); +#endif + + seed_i.bits[0] += 1; + } +} + + +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 = TALER_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); + } +#undef sign + + 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 = TALER_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); + } +#undef verify +} + + +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) + return; + + 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 *acp) +{ + if (NULL == acp) + return; + + if (NULL != acp->proof.keys) + { + GNUNET_CRYPTO_zero_keys ( + acp->proof.keys, + sizeof(*acp->proof.keys) * acp->proof.num); + + GNUNET_free (acp->proof.keys); + acp->proof.keys = NULL; + } + + if (NULL != acp->commitment.keys) + { + GNUNET_free (acp->commitment.keys); + acp->commitment.keys = NULL; + } +} + + +struct TALER_AgeCommitmentProof * +TALER_age_commitment_proof_duplicate ( + const struct TALER_AgeCommitmentProof *acp) +{ + struct TALER_AgeCommitmentProof *nacp; + + GNUNET_assert (NULL != acp); + GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 == + (int) acp->commitment.num); + + nacp = GNUNET_new (struct TALER_AgeCommitmentProof); + + TALER_age_commitment_proof_deep_copy (acp,nacp); + return nacp; +} + + +void +TALER_age_commitment_proof_deep_copy ( + const struct TALER_AgeCommitmentProof *acp, + struct TALER_AgeCommitmentProof *nacp) +{ + GNUNET_assert (NULL != acp); + GNUNET_assert (__builtin_popcount (acp->commitment.mask.bits) - 1 == + (int) acp->commitment.num); + + *nacp = *acp; + nacp->commitment.keys = + GNUNET_new_array (acp->commitment.num, + struct TALER_AgeCommitmentPublicKeyP); + nacp->proof.keys = + GNUNET_new_array (acp->proof.num, + struct TALER_AgeCommitmentPrivateKeyP); + + for (size_t i = 0; i < acp->commitment.num; i++) + nacp->commitment.keys[i] = acp->commitment.keys[i]; + + for (size_t i = 0; i < acp->proof.num; i++) + nacp->proof.keys[i] = acp->proof.keys[i]; +} + + +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; + + /* reset mask */ + mask->bits = 0; + + 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'<c) + return GNUNET_SYSERR; + + val = 10 * val + c - '0'; + + if (0>=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; +} + + +const char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *mask) +{ + static char buf[256] = {0}; + uint32_t bits = mask->bits; + unsigned int n = 0; + char *pos = buf; + + memset (buf, 0, sizeof(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; +} + + +void +TALER_age_restriction_from_secret ( + const struct TALER_PlanchetMasterSecretP *secret, + const struct TALER_AgeMask *mask, + const 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 != secret); + GNUNET_assert (NULL != ncp); + GNUNET_assert (mask->bits & 1); /* fist bit must have been set */ + + num_pub = __builtin_popcount (mask->bits) - 1; + num_priv = TALER_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 multiplication 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), + secret, sizeof(*secret), + 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 + ecdsa_create_from_seed (&seed_i, + sizeof(seed_i), + &pkey->priv); + 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 + } + } +} + + +enum GNUNET_GenericReturnValue +TALER_parse_coarse_date ( + const char *in, + const struct TALER_AgeMask *mask, + uint32_t *out) +{ + struct tm date = {0}; + struct tm limit = {0}; + time_t seconds; + + if (NULL == in) + { + /* FIXME[oec]: correct behaviour? */ + *out = 0; + return GNUNET_OK; + } + + GNUNET_assert (NULL !=mask); + GNUNET_assert (NULL !=out); + + if (NULL == strptime (in, "%Y-%m-%d", &date)) + { + if (NULL == strptime (in, "%Y-%m-00", &date)) + if (NULL == strptime (in, "%Y-00-00", &date)) + return GNUNET_SYSERR; + /* turns out that the day is off by one in the last two cases */ + date.tm_mday += 1; + } + + seconds = timegm (&date); + if (-1 == seconds) + return GNUNET_SYSERR; + + /* calculate the limit date for the largest age group */ + { + time_t l = time (NULL); + localtime_r (&l, &limit); + } + limit.tm_year -= TALER_adult_age (mask); + GNUNET_assert (-1 != timegm (&limit)); + + if ((limit.tm_year < date.tm_year) + || ((limit.tm_year == date.tm_year) + && (limit.tm_mon < date.tm_mon)) + || ((limit.tm_year == date.tm_year) + && (limit.tm_mon == date.tm_mon) + && (limit.tm_mday < date.tm_mday))) + *out = seconds / 60 / 60 / 24; + else + *out = 0; + + return GNUNET_OK; +} + + +/* end util/age_restriction.c */ diff --git a/src/util/aml_signatures.c b/src/util/aml_signatures.c new file mode 100644 index 000000000..a61646c0d --- /dev/null +++ b/src/util/aml_signatures.c @@ -0,0 +1,201 @@ +/* + This file is part of TALER + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file aml_signatures.c + * @brief Utility functions for AML officers + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on an AML decision. + */ +struct TALER_AmlDecisionPS +{ + /** + * Purpose must be #TALER_SIGNATURE_AML_DECISION. + * Used for an EdDSA signature with the `struct TALER_AmlOfficerPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the justification text. + */ + struct GNUNET_HashCode h_justification GNUNET_PACKED; + + /** + * Time when this decision was made. + */ + struct GNUNET_TIME_TimestampNBO decision_time; + + /** + * New threshold for triggering possibly a new AML process. + */ + struct TALER_AmountNBO new_threshold; + + /** + * Hash of the account identifier to which the decision applies. + */ + struct TALER_PaytoHashP h_payto GNUNET_PACKED; + + /** + * Hash over JSON array with KYC requirements that were imposed. All zeros + * for none. + */ + struct GNUNET_HashCode h_kyc_requirements; + + /** + * What is the new AML status? + */ + uint32_t new_state GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + +void +TALER_officer_aml_decision_sign ( + const char *justification, + struct GNUNET_TIME_Timestamp decision_time, + const struct TALER_Amount *new_threshold, + const struct TALER_PaytoHashP *h_payto, + enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + struct TALER_AmlOfficerSignatureP *officer_sig) +{ + struct TALER_AmlDecisionPS ad = { + .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION), + .purpose.size = htonl (sizeof (ad)), + .decision_time = GNUNET_TIME_timestamp_hton (decision_time), + .h_payto = *h_payto, + .new_state = htonl ((uint32_t) new_state) + }; + + GNUNET_CRYPTO_hash (justification, + strlen (justification), + &ad.h_justification); + TALER_amount_hton (&ad.new_threshold, + new_threshold); + if (NULL != kyc_requirements) + TALER_json_hash (kyc_requirements, + &ad.h_kyc_requirements); + GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv, + &ad, + &officer_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_officer_aml_decision_verify ( + const char *justification, + struct GNUNET_TIME_Timestamp decision_time, + const struct TALER_Amount *new_threshold, + const struct TALER_PaytoHashP *h_payto, + enum TALER_AmlDecisionState new_state, + const json_t *kyc_requirements, + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const struct TALER_AmlOfficerSignatureP *officer_sig) +{ + struct TALER_AmlDecisionPS ad = { + .purpose.purpose = htonl (TALER_SIGNATURE_AML_DECISION), + .purpose.size = htonl (sizeof (ad)), + .decision_time = GNUNET_TIME_timestamp_hton (decision_time), + .h_payto = *h_payto, + .new_state = htonl ((uint32_t) new_state) + }; + + GNUNET_CRYPTO_hash (justification, + strlen (justification), + &ad.h_justification); + TALER_amount_hton (&ad.new_threshold, + new_threshold); + if (NULL != kyc_requirements) + TALER_json_hash (kyc_requirements, + &ad.h_kyc_requirements); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_AML_DECISION, + &ad, + &officer_sig->eddsa_signature, + &officer_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on any AML query. + */ +struct TALER_AmlQueryPS +{ + /** + * Purpose must be #TALER_SIGNATURE_AML_QUERY. + * Used for an EdDSA signature with the `struct TALER_AmlOfficerPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_officer_aml_query_sign ( + const struct TALER_AmlOfficerPrivateKeyP *officer_priv, + struct TALER_AmlOfficerSignatureP *officer_sig) +{ + struct TALER_AmlQueryPS aq = { + .purpose.purpose = htonl (TALER_SIGNATURE_AML_QUERY), + .purpose.size = htonl (sizeof (aq)) + }; + + GNUNET_CRYPTO_eddsa_sign (&officer_priv->eddsa_priv, + &aq, + &officer_sig->eddsa_signature); +} + + +/** + * Verify AML query authorization. + * + * @param officer_pub public key of AML officer + * @param officer_sig signature to verify + * @return #GNUNET_OK if the signature is valid + */ +enum GNUNET_GenericReturnValue +TALER_officer_aml_query_verify ( + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const struct TALER_AmlOfficerSignatureP *officer_sig) +{ + struct TALER_AmlQueryPS aq = { + .purpose.purpose = htonl (TALER_SIGNATURE_AML_QUERY), + .purpose.size = htonl (sizeof (aq)) + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_AML_QUERY, + &aq, + &officer_sig->eddsa_signature, + &officer_pub->eddsa_pub); +} + + +/* end of aml_signatures.c */ diff --git a/src/util/amount.c b/src/util/amount.c index ae9ae652e..cce84d73a 100644 --- a/src/util/amount.c +++ b/src/util/amount.c @@ -40,6 +40,31 @@ invalidate (struct TALER_Amount *a) enum GNUNET_GenericReturnValue +TALER_check_currency (const char *str) +{ + if (strlen (str) >= TALER_CURRENCY_LEN) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Currency code name `%s' is too long\n", + str); + 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]) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Currency code name `%s' contains illegal characters (only A-Z allowed)\n", + str); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue TALER_string_to_amount (const char *str, struct TALER_Amount *amount) { @@ -62,6 +87,7 @@ TALER_string_to_amount (const char *str, /* parse currency */ colon = strchr (str, (int) ':'); if ( (NULL == colon) || + (colon == str) || ((colon - str) >= TALER_CURRENCY_LEN) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -72,14 +98,15 @@ TALER_string_to_amount (const char *str, } GNUNET_assert (TALER_CURRENCY_LEN > (colon - str)); - memcpy (amount->currency, - str, - colon - str); + 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]) @@ -193,9 +220,8 @@ TALER_amount_hton (struct TALER_AmountNBO *res, TALER_amount_is_valid (d)); res->value = GNUNET_htonll (d->value); res->fraction = htonl (d->fraction); - memcpy (res->currency, - d->currency, - TALER_CURRENCY_LEN); + for (unsigned int i = 0; i<TALER_CURRENCY_LEN; i++) + res->currency[i] = d->currency[i]; } @@ -205,9 +231,9 @@ TALER_amount_ntoh (struct TALER_Amount *res, { res->value = GNUNET_ntohll (dn->value); res->fraction = ntohl (dn->fraction); - memcpy (res->currency, - dn->currency, - TALER_CURRENCY_LEN); + GNUNET_memcpy (res->currency, + dn->currency, + TALER_CURRENCY_LEN); GNUNET_assert (GNUNET_YES == TALER_amount_is_valid (res)); } @@ -219,15 +245,15 @@ TALER_amount_set_zero (const char *cur, { size_t slen; - slen = strlen (cur); - if (slen >= TALER_CURRENCY_LEN) + if (GNUNET_OK != + TALER_check_currency (cur)) return GNUNET_SYSERR; + slen = strlen (cur); memset (amount, 0, sizeof (struct TALER_Amount)); - memcpy (amount->currency, - cur, - slen); + for (unsigned int i = 0; i<slen; i++) + amount->currency[i] = cur[i]; return GNUNET_OK; } @@ -236,7 +262,10 @@ enum GNUNET_GenericReturnValue TALER_amount_is_valid (const struct TALER_Amount *amount) { if (amount->value > TALER_AMOUNT_MAX_VALUE) + { + GNUNET_break (0); return GNUNET_SYSERR; + } return ('\0' != amount->currency[0]) ? GNUNET_OK : GNUNET_NO; } @@ -253,6 +282,20 @@ TALER_amount_is_zero (const struct TALER_Amount *amount) } +enum GNUNET_GenericReturnValue +TALER_amount_is_currency (const struct TALER_Amount *amount, + const char *currency) +{ + if (GNUNET_OK != + TALER_amount_is_valid (amount)) + return GNUNET_SYSERR; + return (0 == strcasecmp (currency, + amount->currency)) + ? GNUNET_OK + : GNUNET_NO; +} + + /** * Test if @a a is valid, NBO variant. * @@ -539,8 +582,8 @@ const char * TALER_amount2s (const struct TALER_Amount *amount) { /* 24 is sufficient for a uint64_t value in decimal; 3 is for ":.\0" */ - static GNUNET_THREAD_LOCAL char result[TALER_AMOUNT_FRAC_LEN - + TALER_CURRENCY_LEN + 3 + 24]; + static TALER_THREAD_LOCAL char result[TALER_AMOUNT_FRAC_LEN + + TALER_CURRENCY_LEN + 3 + 24]; struct TALER_Amount norm; if (GNUNET_YES != TALER_amount_is_valid (amount)) @@ -666,9 +709,9 @@ TALER_amount_multiply (struct TALER_Amount *result, if (GNUNET_SYSERR == TALER_amount_normalize (&in)) return TALER_AAR_INVALID_NORMALIZATION_FAILED; - memcpy (result->currency, - amount->currency, - TALER_CURRENCY_LEN); + GNUNET_memcpy (result->currency, + amount->currency, + TALER_CURRENCY_LEN); if ( (0 == factor) || ( (0 == in.value) && (0 == in.fraction) ) ) diff --git a/src/util/auditor_signatures.c b/src/util/auditor_signatures.c index 6ca1b046d..c35b6f192 100644 --- a/src/util/auditor_signatures.c +++ b/src/util/auditor_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020, 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 @@ -23,44 +23,118 @@ #include "taler_signatures.h" +/** + * @brief Information signed by an auditor affirming + * the master public key and the denomination keys + * of a exchange. + */ +struct TALER_ExchangeKeyValidityPS +{ + + /** + * Purpose is #TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the auditor's URL (including 0-terminator). + */ + struct GNUNET_HashCode auditor_url_hash; + + /** + * The long-term offline master key of the exchange, affirmed by the + * auditor. + */ + struct TALER_MasterPublicKeyP master; + + /** + * Start time of the validity period for this key. + */ + struct GNUNET_TIME_TimestampNBO start; + + /** + * The exchange will sign fresh coins between @e start and this time. + * @e expire_withdraw will be somewhat larger than @e start to + * ensure a sufficiently large anonymity set, while also allowing + * the Exchange to limit the financial damage in case of a key being + * compromised. Thus, exchanges with low volume are expected to have a + * longer withdraw period (@e expire_withdraw - @e start) than exchanges + * with high transaction volume. The period may also differ between + * types of coins. A exchange may also have a few denomination keys + * with the same value with overlapping validity periods, to address + * issues such as clock skew. + */ + struct GNUNET_TIME_TimestampNBO expire_withdraw; + + /** + * Coins signed with the denomination key must be spent or refreshed + * between @e start and this expiration time. After this time, the + * exchange will refuse transactions involving this key as it will + * "drop" the table with double-spending information (shortly after) + * this time. Note that wallets should refresh coins significantly + * before this time to be on the safe side. @e expire_deposit must be + * significantly larger than @e expire_withdraw (by months or even + * years). + */ + struct GNUNET_TIME_TimestampNBO expire_deposit; + + /** + * When do signatures with this denomination key become invalid? + * After this point, these signatures cannot be used in (legal) + * disputes anymore, as the Exchange is then allowed to destroy its side + * of the evidence. @e expire_legal is expected to be significantly + * larger than @e expire_deposit (by a year or more). + */ + struct GNUNET_TIME_TimestampNBO expire_legal; + + /** + * The value of the coins signed with this denomination key. + */ + struct TALER_AmountNBO value; + + /** + * Fees for the coin. + */ + struct TALER_DenomFeeSetNBOP fees; + + /** + * Hash code of the denomination public key. (Used to avoid having + * the variable-size RSA key in this struct.) + */ + struct TALER_DenominationHashP denom_hash GNUNET_PACKED; + +}; + + void TALER_auditor_denom_validity_sign ( const char *auditor_url, - const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_MasterPublicKeyP *master_pub, - struct GNUNET_TIME_Absolute stamp_start, - struct GNUNET_TIME_Absolute stamp_expire_withdraw, - struct GNUNET_TIME_Absolute stamp_expire_deposit, - struct GNUNET_TIME_Absolute stamp_expire_legal, + struct GNUNET_TIME_Timestamp stamp_start, + struct GNUNET_TIME_Timestamp stamp_expire_withdraw, + struct GNUNET_TIME_Timestamp stamp_expire_deposit, + struct GNUNET_TIME_Timestamp stamp_expire_legal, const struct TALER_Amount *coin_value, - const struct TALER_Amount *fee_withdraw, - const struct TALER_Amount *fee_deposit, - const struct TALER_Amount *fee_refresh, - const struct TALER_Amount *fee_refund, + const struct TALER_DenomFeeSet *fees, const struct TALER_AuditorPrivateKeyP *auditor_priv, struct TALER_AuditorSignatureP *auditor_sig) { struct TALER_ExchangeKeyValidityPS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), .purpose.size = htonl (sizeof (kv)), - .start = GNUNET_TIME_absolute_hton (stamp_start), - .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), - .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .start = GNUNET_TIME_timestamp_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal), .denom_hash = *h_denom_pub, .master = *master_pub, }; TALER_amount_hton (&kv.value, coin_value); - TALER_amount_hton (&kv.fee_withdraw, - fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - fee_refresh); - TALER_amount_hton (&kv.fee_refund, - fee_refund); + TALER_denom_fee_set_hton (&kv.fees, + fees); GNUNET_CRYPTO_hash (auditor_url, strlen (auditor_url) + 1, &kv.auditor_url_hash); @@ -73,41 +147,32 @@ TALER_auditor_denom_validity_sign ( enum GNUNET_GenericReturnValue TALER_auditor_denom_validity_verify ( const char *auditor_url, - const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_MasterPublicKeyP *master_pub, - struct GNUNET_TIME_Absolute stamp_start, - struct GNUNET_TIME_Absolute stamp_expire_withdraw, - struct GNUNET_TIME_Absolute stamp_expire_deposit, - struct GNUNET_TIME_Absolute stamp_expire_legal, + struct GNUNET_TIME_Timestamp stamp_start, + struct GNUNET_TIME_Timestamp stamp_expire_withdraw, + struct GNUNET_TIME_Timestamp stamp_expire_deposit, + struct GNUNET_TIME_Timestamp stamp_expire_legal, const struct TALER_Amount *coin_value, - const struct TALER_Amount *fee_withdraw, - const struct TALER_Amount *fee_deposit, - const struct TALER_Amount *fee_refresh, - const struct TALER_Amount *fee_refund, + const struct TALER_DenomFeeSet *fees, const struct TALER_AuditorPublicKeyP *auditor_pub, const struct TALER_AuditorSignatureP *auditor_sig) { struct TALER_ExchangeKeyValidityPS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS), .purpose.size = htonl (sizeof (kv)), - .start = GNUNET_TIME_absolute_hton (stamp_start), - .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), - .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .start = GNUNET_TIME_timestamp_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal), .denom_hash = *h_denom_pub, .master = *master_pub, }; TALER_amount_hton (&kv.value, coin_value); - TALER_amount_hton (&kv.fee_withdraw, - fee_withdraw); - TALER_amount_hton (&kv.fee_deposit, - fee_deposit); - TALER_amount_hton (&kv.fee_refresh, - fee_refresh); - TALER_amount_hton (&kv.fee_refund, - fee_refund); + TALER_denom_fee_set_hton (&kv.fees, + fees); GNUNET_CRYPTO_hash (auditor_url, strlen (auditor_url) + 1, &kv.auditor_url_hash); diff --git a/src/util/bench_age_restriction.c b/src/util/bench_age_restriction.c new file mode 100644 index 000000000..abda9416a --- /dev/null +++ b/src/util/bench_age_restriction.c @@ -0,0 +1,208 @@ +/** + * @file util/bench_age_restriction.c + * @brief Measure Commit, Attest, Verify, Derive and Compare + * @author Özgür Kesim + * + * compile in exchange/src/util with + * + * gcc benc_age_restriction.c \ + * -lgnunetutil -lgnunetjson -lsodium -ljansson \ + * -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil \ + * -I../include \ + * -o bench_age_restriction + * + */ +#include "platform.h" +#include <math.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include <taler/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); + +/** + * Encodes the age mask into a string, like "8:10:12:14:16:18:21" + */ +char * +age_mask_to_string ( + const struct TALER_AgeMask *m) +{ + uint32_t bits = m->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; +} + + +#define ITER 2000 + +double +average (long *times, size_t size) +{ + double mean = 0.0; + for (int i = 0; i < size; i++) + { + mean += times[i]; + } + return mean / size; +} + + +double +stdev (long *times, size_t size) +{ + double mean = average (times, size); + double V = 0.0; + for (int i = 0; i < size; i++) + { + double d = times[i] - mean; + d *= d; + V += d; + } + return sqrt (V / size); +} + + +#define pr(n,t, i) printf ("%10s (%dx):\t%.2f ± %.2fµs\n", (n), i, average ( \ + &t[0], ITER) / 1000, stdev (&t[0], ITER) / 1000); \ + i = 0; + +#define starttime clock_gettime (CLOCK_MONOTONIC, &tstart) +#define stoptime clock_gettime (CLOCK_MONOTONIC, &tend); \ + times[i] = ((long) tend.tv_sec * 1000 * 1000 * 1000 + tend.tv_nsec) \ + - ((long) tstart.tv_sec * 1000 * 1000 * 1000 + tstart.tv_nsec); + + +int +main (int argc, + const char *const argv[]) +{ + struct timespec tstart = {0,0}, tend = {0,0}; + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeCommitmentProof acp = {0}; + uint8_t age = 21; + uint8_t age_group = get_age_group (&age_mask, age); + struct GNUNET_HashCode seed; + long times[ITER] = {0}; + int i = 0; + + // commit + for (; i < ITER; i++) + { + starttime; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + + ret = TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp); + stoptime; + + } + pr ("commit", times, i); + + // attest + for (; i < ITER; i++) + { + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + + ret = TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp); + + starttime; + uint8_t min_group = get_age_group (&age_mask, 13); + struct TALER_AgeAttestation at = {0}; + ret = TALER_age_commitment_attest (&acp, + 13, + &at); + stoptime; + } + pr ("attest", times, i); + + // verify + for (; i < ITER; i++) + { + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + + ret = TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp); + + uint8_t min_group = get_age_group (&age_mask, 13); + struct TALER_AgeAttestation at = {0}; + + ret = TALER_age_commitment_attest (&acp, + 13, + &at); + starttime; + ret = TALER_age_commitment_verify (&acp.commitment, + 13, + &at); + stoptime; + } + pr ("verify", times, i); + + // derive + for (; i < ITER; i++) + { + struct TALER_AgeCommitmentProof acp2 = {0}; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + starttime; + TALER_age_commitment_derive (&acp, + &seed, + &acp2); + stoptime; + } + pr ("derive", times, i); + + return 0; +} + + +/* end of tv_age_restriction.c */ diff --git a/src/util/config.c b/src/util/config.c index d368d346e..f5accaad8 100644 --- a/src/util/config.c +++ b/src/util/config.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-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 @@ -21,17 +21,8 @@ */ #include "platform.h" #include "taler_util.h" +#include <gnunet/gnunet_json_lib.h> - -/** - * Obtain denomination amount from configuration file. - * - * @param cfg configuration to use - * @param section section of the configuration to access - * @param option option of the configuration to access - * @param[out] denom set to the amount found in configuration - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ enum GNUNET_GenericReturnValue TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section, @@ -67,18 +58,80 @@ TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg, } -/** - * Load our currency from the @a cfg (in section [taler] - * the option "CURRENCY"). - * - * @param cfg configuration to use - * @param[out] currency where to write the result - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ +enum GNUNET_GenericReturnValue +TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *currency, + const char *section, + struct TALER_DenomFeeSet *fees) +{ + if (GNUNET_OK != + TALER_config_get_amount (cfg, + section, + "FEE_WITHDRAW", + &fees->withdraw)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "Need amount for option `%s' in section `%s'\n", + "FEE_WITHDRAW", + section); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_amount (cfg, + section, + "FEE_DEPOSIT", + &fees->deposit)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "Need amount for option `%s' in section `%s'\n", + "FEE_DEPOSIT", + section); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_amount (cfg, + section, + "FEE_REFRESH", + &fees->refresh)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "Need amount for option `%s' in section `%s'\n", + "FEE_REFRESH", + section); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_amount (cfg, + section, + "FEE_REFUND", + &fees->refund)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "Need amount for option `%s' in section `%s'\n", + "FEE_REFUND", + section); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_denom_fee_check_currency (currency, + fees)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Need fee amounts in section `%s' to use currency `%s'\n", + section, + currency); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + enum GNUNET_GenericReturnValue TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg, char **currency) { + size_t slen; + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", @@ -90,7 +143,8 @@ TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg, "CURRENCY"); return GNUNET_SYSERR; } - if (strlen (*currency) >= TALER_CURRENCY_LEN) + slen = strlen (*currency); + if (slen >= TALER_CURRENCY_LEN) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Currency `%s' longer than the allowed limit of %u characters.", @@ -100,5 +154,357 @@ TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg, *currency = NULL; return GNUNET_SYSERR; } + for (size_t i = 0; i<slen; i++) + if (! isalpha ((unsigned char) (*currency)[i])) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Currency `%s' must only use characters from the A-Z range.", + *currency); + GNUNET_free (*currency); + *currency = NULL; + return GNUNET_SYSERR; + } return GNUNET_OK; } + + +/** + * Closure for #parse_currencies_cb(). + */ +struct CurrencyParserContext +{ + /** + * Current offset in @e cspecs. + */ + unsigned int num_currencies; + + /** + * Length of the @e cspecs array. + */ + unsigned int len_cspecs; + + /** + * Array of currency specifications (see DD 51). + */ + struct TALER_CurrencySpecification *cspecs; + + /** + * Configuration we are parsing. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Set to true if the configuration was malformed. + */ + bool failure; +}; + + +/** + * Function to iterate over section. + * + * @param cls closure with a `struct CurrencyParserContext *` + * @param section name of the section + */ +static void +parse_currencies_cb (void *cls, + const char *section) +{ + struct CurrencyParserContext *cpc = cls; + struct TALER_CurrencySpecification *cspec; + unsigned long long num; + char *str; + + if (cpc->failure) + return; + if (0 != strncasecmp (section, + "currency-", + strlen ("currency-"))) + return; /* not interesting */ + if (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cpc->cfg, + section, + "ENABLED")) + return; /* disabled */ + if (cpc->len_cspecs == cpc->num_currencies) + { + GNUNET_array_grow (cpc->cspecs, + cpc->len_cspecs, + cpc->len_cspecs * 2 + 4); + } + cspec = &cpc->cspecs[cpc->num_currencies++]; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cpc->cfg, + section, + "CODE", + &str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "CODE"); + cpc->failure = true; + return; + } + if (GNUNET_OK != + TALER_check_currency (str)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "CODE", + "Currency code name given is invalid"); + cpc->failure = true; + GNUNET_free (str); + return; + } + memset (cspec->currency, + 0, + sizeof (cspec->currency)); + /* Already checked in TALER_check_currency(), repeated here + just to make static analysis happy */ + GNUNET_assert (strlen (str) < TALER_CURRENCY_LEN); + strcpy (cspec->currency, + str); + GNUNET_free (str); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cpc->cfg, + section, + "NAME", + &str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "NAME"); + cpc->failure = true; + return; + } + cspec->name = str; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cpc->cfg, + section, + "FRACTIONAL_INPUT_DIGITS", + &num)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_INPUT_DIGITS"); + cpc->failure = true; + return; + } + if (num > TALER_AMOUNT_FRAC_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_INPUT_DIGITS", + "Number given is too big"); + cpc->failure = true; + return; + } + cspec->num_fractional_input_digits = num; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cpc->cfg, + section, + "FRACTIONAL_NORMAL_DIGITS", + &num)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_NORMAL_DIGITS"); + cpc->failure = true; + return; + } + if (num > TALER_AMOUNT_FRAC_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_NORMAL_DIGITS", + "Number given is too big"); + cpc->failure = true; + return; + } + cspec->num_fractional_normal_digits = num; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cpc->cfg, + section, + "FRACTIONAL_TRAILING_ZERO_DIGITS", + &num)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_TRAILING_ZERO_DIGITS"); + cpc->failure = true; + return; + } + if (num > TALER_AMOUNT_FRAC_LEN) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "FRACTIONAL_TRAILING_ZERO_DIGITS", + "Number given is too big"); + cpc->failure = true; + return; + } + cspec->num_fractional_trailing_zero_digits = num; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cpc->cfg, + section, + "ALT_UNIT_NAMES", + &str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "ALT_UNIT_NAMES"); + cpc->failure = true; + return; + } + { + json_error_t err; + + cspec->map_alt_unit_names = json_loads (str, + JSON_REJECT_DUPLICATES, + &err); + GNUNET_free (str); + if (NULL == cspec->map_alt_unit_names) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "ALT_UNIT_NAMES", + err.text); + cpc->failure = true; + return; + } + } + if (GNUNET_OK != + TALER_check_currency_scale_map (cspec->map_alt_unit_names)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "ALT_UNIT_NAMES", + "invalid map entry detected"); + cpc->failure = true; + json_decref (cspec->map_alt_unit_names); + cspec->map_alt_unit_names = NULL; + return; + } +} + + +enum GNUNET_GenericReturnValue +TALER_check_currency_scale_map (const json_t *map) +{ + /* validate map only maps from decimal numbers to strings! */ + const char *str; + const json_t *val; + bool zf = false; + + if (! json_is_object (map)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Object required for currency scale map\n"); + return GNUNET_SYSERR; + } + json_object_foreach ((json_t *) map, str, val) + { + int idx; + char dummy; + + if ( (1 != sscanf (str, + "%d%c", + &idx, + &dummy)) || + (idx < -12) || + (idx > 24) || + (! json_is_string (val) ) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid entry `%s' in currency scale map\n", + str); + return GNUNET_SYSERR; + } + if (0 == idx) + zf = true; + } + if (! zf) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Entry for 0 missing in currency scale map\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_CONFIG_parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg, + unsigned int *num_currencies, + struct TALER_CurrencySpecification **cspecs) +{ + struct CurrencyParserContext cpc = { + .cfg = cfg + }; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_currencies_cb, + &cpc); + if (cpc.failure) + { + GNUNET_array_grow (cpc.cspecs, + cpc.len_cspecs, + 0); + return GNUNET_SYSERR; + } + GNUNET_array_grow (cpc.cspecs, + cpc.len_cspecs, + cpc.num_currencies); + *num_currencies = cpc.num_currencies; + *cspecs = cpc.cspecs; + if (0 == *num_currencies) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No currency formatting specification found! Please check your installation!\n"); + return GNUNET_NO; + } + return GNUNET_OK; +} + + +json_t * +TALER_CONFIG_currency_specs_to_json (const struct + TALER_CurrencySpecification *cspec) +{ + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("name", + cspec->name), + /* 'currency' is deprecated as of exchange v18 and merchant v6; + remove this line once current-age > 6*/ + GNUNET_JSON_pack_string ("currency", + cspec->currency), + GNUNET_JSON_pack_uint64 ("num_fractional_input_digits", + cspec->num_fractional_input_digits), + GNUNET_JSON_pack_uint64 ("num_fractional_normal_digits", + cspec->num_fractional_normal_digits), + GNUNET_JSON_pack_uint64 ("num_fractional_trailing_zero_digits", + cspec->num_fractional_trailing_zero_digits), + GNUNET_JSON_pack_object_incref ("alt_unit_names", + cspec->map_alt_unit_names)); +} + + +void +TALER_CONFIG_free_currencies ( + unsigned int num_currencies, + struct TALER_CurrencySpecification cspecs[static num_currencies]) +{ + for (unsigned int i = 0; i<num_currencies; i++) + { + struct TALER_CurrencySpecification *cspec = &cspecs[i]; + + GNUNET_free (cspec->name); + json_decref (cspec->map_alt_unit_names); + } + GNUNET_array_grow (cspecs, + num_currencies, + 0); +} diff --git a/src/util/conversion.c b/src/util/conversion.c new file mode 100644 index 000000000..a7bc63789 --- /dev/null +++ b/src/util/conversion.c @@ -0,0 +1,405 @@ +/* + This file is part of TALER + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file conversion.c + * @brief helper routines to run some external JSON-to-JSON converter + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include <gnunet/gnunet_util_lib.h> + + +struct TALER_JSON_ExternalConversion +{ + /** + * Callback to call with the result. + */ + TALER_JSON_JsonCallback cb; + + /** + * Closure for @e cb. + */ + void *cb_cls; + + /** + * Handle to the helper process. + */ + struct GNUNET_OS_Process *helper; + + /** + * Pipe for the stdin of the @e helper. + */ + struct GNUNET_DISK_FileHandle *chld_stdin; + + /** + * Pipe for the stdout of the @e helper. + */ + struct GNUNET_DISK_FileHandle *chld_stdout; + + /** + * Handle to wait on the child to terminate. + */ + struct GNUNET_ChildWaitHandle *cwh; + + /** + * Task to read JSON output from the child. + */ + struct GNUNET_SCHEDULER_Task *read_task; + + /** + * Task to send JSON input to the child. + */ + struct GNUNET_SCHEDULER_Task *write_task; + + /** + * Buffer with data we need to send to the helper. + */ + void *write_buf; + + /** + * Buffer for reading data from the helper. + */ + void *read_buf; + + /** + * Total length of @e write_buf. + */ + size_t write_size; + + /** + * Current write position in @e write_buf. + */ + size_t write_pos; + + /** + * Current size of @a read_buf. + */ + size_t read_size; + + /** + * Current offset in @a read_buf. + */ + size_t read_pos; + +}; + + +/** + * Function called when we can read more data from + * the child process. + * + * @param cls our `struct TALER_JSON_ExternalConversion *` + */ +static void +read_cb (void *cls) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + + ec->read_task = NULL; + while (1) + { + ssize_t ret; + + if (ec->read_size == ec->read_pos) + { + /* Grow input buffer */ + size_t ns; + void *tmp; + + ns = GNUNET_MAX (2 * ec->read_size, + 1024); + if (ns > GNUNET_MAX_MALLOC_CHECKED) + ns = GNUNET_MAX_MALLOC_CHECKED; + if (ec->read_size == ns) + { + /* Helper returned more than 40 MB of data! Stop reading! */ + GNUNET_break (0); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + return; + } + tmp = GNUNET_malloc_large (ns); + if (NULL == tmp) + { + /* out of memory, also stop reading */ + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + return; + } + GNUNET_memcpy (tmp, + ec->read_buf, + ec->read_pos); + GNUNET_free (ec->read_buf); + ec->read_buf = tmp; + ec->read_size = ns; + } + ret = GNUNET_DISK_file_read (ec->chld_stdout, + ec->read_buf + ec->read_pos, + ec->read_size - ec->read_pos); + if (ret < 0) + { + if ( (EAGAIN != errno) && + (EWOULDBLOCK != errno) && + (EINTR != errno) ) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "read"); + return; + } + break; + } + if (0 == ret) + { + /* regular end of stream, good! */ + return; + } + GNUNET_assert (ec->read_size >= ec->read_pos + ret); + ec->read_pos += ret; + } + ec->read_task + = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdout, + &read_cb, + ec); +} + + +/** + * Function called when we can write more data to + * the child process. + * + * @param cls our `struct TALER_JSON_ExternalConversion *` + */ +static void +write_cb (void *cls) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + ssize_t ret; + + ec->write_task = NULL; + while (ec->write_size > ec->write_pos) + { + ret = GNUNET_DISK_file_write (ec->chld_stdin, + ec->write_buf + ec->write_pos, + ec->write_size - ec->write_pos); + if (ret < 0) + { + if ( (EAGAIN != errno) && + (EINTR != errno) ) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "write"); + break; + } + if (0 == ret) + { + GNUNET_break (0); + break; + } + GNUNET_assert (ec->write_size >= ec->write_pos + ret); + ec->write_pos += ret; + } + if ( (ec->write_size > ec->write_pos) && + ( (EAGAIN == errno) || + (EWOULDBLOCK == errno) || + (EINTR == errno) ) ) + { + ec->write_task + = GNUNET_SCHEDULER_add_write_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdin, + &write_cb, + ec); + } + else + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + ec->chld_stdin = NULL; + } +} + + +/** + * Defines a GNUNET_ChildCompletedCallback which is sent back + * upon death or completion of a child process. + * + * @param cls handle for the callback + * @param type type of the process + * @param exit_code status code of the process + * + */ +static void +child_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct TALER_JSON_ExternalConversion *ec = cls; + json_t *j = NULL; + json_error_t err; + + ec->cwh = NULL; + if (NULL != ec->read_task) + { + GNUNET_SCHEDULER_cancel (ec->read_task); + /* We could get the process termination notification before having drained + the read buffer. So drain it now, just in case. */ + read_cb (ec); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n", + (int) type, + (unsigned long long) exit_code, + (unsigned long long) ec->read_pos); + GNUNET_OS_process_destroy (ec->helper); + ec->helper = NULL; + if (0 != ec->read_pos) + { + j = json_loadb (ec->read_buf, + ec->read_pos, + JSON_REJECT_DUPLICATES, + &err); + if (NULL == j) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to parse JSON from helper at %d: %s\n", + err.position, + err.text); + } + } + ec->cb (ec->cb_cls, + type, + exit_code, + j); + json_decref (j); + TALER_JSON_external_conversion_stop (ec); +} + + +struct TALER_JSON_ExternalConversion * +TALER_JSON_external_conversion_start (const json_t *input, + TALER_JSON_JsonCallback cb, + void *cb_cls, + const char *binary, + ...) +{ + struct TALER_JSON_ExternalConversion *ec; + struct GNUNET_DISK_PipeHandle *pipe_stdin; + struct GNUNET_DISK_PipeHandle *pipe_stdout; + va_list ap; + + ec = GNUNET_new (struct TALER_JSON_ExternalConversion); + ec->cb = cb; + ec->cb_cls = cb_cls; + pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); + GNUNET_assert (NULL != pipe_stdin); + pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); + GNUNET_assert (NULL != pipe_stdout); + va_start (ap, + binary); + ec->helper = GNUNET_OS_start_process_va (GNUNET_OS_INHERIT_STD_ERR, + pipe_stdin, + pipe_stdout, + NULL, + binary, + ap); + va_end (ap); + if (NULL == ec->helper) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to run conversion helper `%s'\n", + binary); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdin)); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdout)); + GNUNET_free (ec); + return NULL; + } + ec->chld_stdin = + GNUNET_DISK_pipe_detach_end (pipe_stdin, + GNUNET_DISK_PIPE_END_WRITE); + ec->chld_stdout = + GNUNET_DISK_pipe_detach_end (pipe_stdout, + GNUNET_DISK_PIPE_END_READ); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdin)); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_pipe_close (pipe_stdout)); + ec->write_buf = json_dumps (input, JSON_COMPACT); + ec->write_size = strlen (ec->write_buf); + ec->read_task + = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdout, + &read_cb, + ec); + ec->write_task + = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, + ec->chld_stdin, + &write_cb, + ec); + ec->cwh = GNUNET_wait_child (ec->helper, + &child_done_cb, + ec); + return ec; +} + + +void +TALER_JSON_external_conversion_stop ( + struct TALER_JSON_ExternalConversion *ec) +{ + if (NULL != ec->cwh) + { + GNUNET_wait_child_cancel (ec->cwh); + ec->cwh = NULL; + } + if (NULL != ec->helper) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ec->helper, + SIGKILL)); + GNUNET_OS_process_destroy (ec->helper); + } + if (NULL != ec->read_task) + { + GNUNET_SCHEDULER_cancel (ec->read_task); + ec->read_task = NULL; + } + if (NULL != ec->write_task) + { + GNUNET_SCHEDULER_cancel (ec->write_task); + ec->write_task = NULL; + } + if (NULL != ec->chld_stdin) + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdin)); + ec->chld_stdin = NULL; + } + if (NULL != ec->chld_stdout) + { + GNUNET_break (GNUNET_OK == + GNUNET_DISK_file_close (ec->chld_stdout)); + ec->chld_stdout = NULL; + } + GNUNET_free (ec->read_buf); + free (ec->write_buf); + GNUNET_free (ec); +} diff --git a/src/util/crypto.c b/src/util/crypto.c index 178db3aad..4735af3b0 100644 --- a/src/util/crypto.c +++ b/src/util/crypto.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2017 Taler Systems SA + Copyright (C) 2014-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 @@ -20,12 +20,12 @@ * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include "taler_util.h" #include <gcrypt.h> - /** * Function called by libgcrypt on serious errors. * Prints an error message and aborts the process. @@ -73,9 +73,9 @@ enum GNUNET_GenericReturnValue TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info, const struct TALER_DenominationPublicKey *denom_pub) { - struct TALER_CoinPubHash c_hash; + struct TALER_CoinPubHashP c_hash; #if ENABLE_SANITY_CHECKS - struct TALER_DenominationHash d_hash; + struct TALER_DenominationHashP d_hash; TALER_denom_pub_hash (denom_pub, &d_hash); @@ -83,12 +83,13 @@ TALER_test_coin_valid (const struct TALER_CoinPublicInfo *coin_public_info, GNUNET_memcmp (&d_hash, &coin_public_info->denom_pub_hash)); #endif - // FIXME-Oec: replace with function that - // also hashes the age vector if we have - // one! - GNUNET_CRYPTO_hash (&coin_public_info->coin_pub, - sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), - &c_hash.hash); + + TALER_coin_pub_hash (&coin_public_info->coin_pub, + coin_public_info->no_age_commitment + ? NULL + : &coin_public_info->h_age_commitment, + &c_hash); + if (GNUNET_OK != TALER_denom_pub_verify (denom_pub, &coin_public_info->denom_sig, @@ -116,7 +117,6 @@ TALER_link_derive_transfer_secret ( GNUNET_CRYPTO_ecdh_eddsa (&trans_priv->ecdhe_priv, &coin_pub.eddsa_pub, &ts->key)); - } @@ -147,9 +147,30 @@ TALER_link_recover_transfer_secret ( void -TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed, - uint32_t coin_num_salt, - struct TALER_PlanchetSecretsP *ps) +TALER_planchet_master_setup_random ( + struct TALER_PlanchetMasterSecretP *ps) +{ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + ps, + sizeof (*ps)); +} + + +void +TALER_refresh_master_setup_random ( + struct TALER_RefreshMasterSecretP *rms) +{ + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + rms, + sizeof (*rms)); +} + + +void +TALER_transfer_secret_to_planchet_secret ( + const struct TALER_TransferSecretP *secret_seed, + uint32_t coin_num_salt, + struct TALER_PlanchetMasterSecretP *ps) { uint32_t be_salt = htonl (coin_num_salt); @@ -167,32 +188,114 @@ TALER_planchet_setup_refresh (const struct TALER_TransferSecretP *secret_seed, void -TALER_planchet_setup_random (struct TALER_PlanchetSecretsP *ps) +TALER_planchet_secret_to_transfer_priv ( + const struct TALER_RefreshMasterSecretP *rms, + const struct TALER_CoinSpendPrivateKeyP *old_coin_priv, + uint32_t cnc_num, + struct TALER_TransferPrivateKeyP *tpriv) { - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, - ps, - sizeof (*ps)); + uint32_t be_salt = htonl (cnc_num); + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_kdf (tpriv, + sizeof (*tpriv), + &be_salt, + sizeof (be_salt), + old_coin_priv, + sizeof (*old_coin_priv), + rms, + sizeof (*rms), + "taler-transfer-priv-derivation", + strlen ("taler-transfer-priv-derivation"), + NULL, 0)); +} + + +void +TALER_cs_withdraw_nonce_derive ( + const struct TALER_PlanchetMasterSecretP *ps, + struct GNUNET_CRYPTO_CsSessionNonce *nonce) +{ + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (nonce, + sizeof (*nonce), + "n", + strlen ("n"), + ps, + sizeof(*ps), + NULL, + 0)); +} + + +void +TALER_cs_refresh_nonce_derive ( + const struct TALER_RefreshMasterSecretP *rms, + uint32_t coin_num_salt, + struct GNUNET_CRYPTO_CsSessionNonce *nonce) +{ + uint32_t be_salt = htonl (coin_num_salt); + + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (nonce, + sizeof (*nonce), + &be_salt, + sizeof (be_salt), + "refresh-n", + strlen ("refresh-n"), + rms, + sizeof(*rms), + NULL, + 0)); +} + + +void +TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa, + struct TALER_RsaPubHashP *h_rsa) +{ + GNUNET_CRYPTO_rsa_public_key_hash (rsa, + &h_rsa->hash); + +} + + +void +TALER_cs_pub_hash (const struct GNUNET_CRYPTO_CsPublicKey *cs, + struct TALER_CsPubHashP *h_cs) +{ + GNUNET_CRYPTO_hash (cs, + sizeof(*cs), + &h_cs->hash); } enum GNUNET_GenericReturnValue TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, - const struct TALER_PlanchetSecretsP *ps, - struct TALER_CoinPubHash *c_hash, - struct TALER_PlanchetDetail *pd) + const struct TALER_ExchangeWithdrawValues *alg_values, + const union GNUNET_CRYPTO_BlindingSecretP *bks, + const union GNUNET_CRYPTO_BlindSessionNonce *nonce, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_AgeCommitmentHash *ach, + struct TALER_CoinPubHashP *c_hash, + struct TALER_PlanchetDetail *pd + ) { struct TALER_CoinSpendPublicKeyP coin_pub; - GNUNET_CRYPTO_eddsa_key_get_public (&ps->coin_priv.eddsa_priv, + GNUNET_assert (alg_values->blinding_inputs->cipher == + dk->bsign_pub_key->cipher); + GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &coin_pub.eddsa_pub); if (GNUNET_OK != TALER_denom_blind (dk, - &ps->blinding_key, - NULL, /* FIXME-Oec */ + bks, + nonce, + ach, &coin_pub, + alg_values, c_hash, - &pd->coin_ev, - &pd->coin_ev_size)) + &pd->blinded_planchet)) { GNUNET_break (0); return GNUNET_SYSERR; @@ -203,20 +306,42 @@ TALER_planchet_prepare (const struct TALER_DenominationPublicKey *dk, } +void +TALER_planchet_detail_free (struct TALER_PlanchetDetail *pd) +{ + TALER_blinded_planchet_free (&pd->blinded_planchet); +} + + enum GNUNET_GenericReturnValue TALER_planchet_to_coin ( const struct TALER_DenominationPublicKey *dk, const struct TALER_BlindedDenominationSignature *blind_sig, - const struct TALER_PlanchetSecretsP *ps, - const struct TALER_CoinPubHash *c_hash, + const union GNUNET_CRYPTO_BlindingSecretP *bks, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + const struct TALER_AgeCommitmentHash *ach, + const struct TALER_CoinPubHashP *c_hash, + const struct TALER_ExchangeWithdrawValues *alg_values, struct TALER_FreshCoin *coin) { - struct TALER_DenominationSignature sig; - + if (dk->bsign_pub_key->cipher != + blind_sig->blinded_sig->cipher) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (dk->bsign_pub_key->cipher != + alg_values->blinding_inputs->cipher) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } if (GNUNET_OK != - TALER_denom_sig_unblind (&sig, + TALER_denom_sig_unblind (&coin->sig, blind_sig, - &ps->blinding_key, + bks, + c_hash, + alg_values, dk)) { GNUNET_break_op (0); @@ -224,15 +349,16 @@ TALER_planchet_to_coin ( } if (GNUNET_OK != TALER_denom_pub_verify (dk, - &sig, + &coin->sig, c_hash)) { GNUNET_break_op (0); - TALER_denom_sig_free (&sig); + TALER_denom_sig_free (&coin->sig); return GNUNET_SYSERR; } - coin->sig = sig; - coin->coin_priv = ps->coin_priv; + + coin->coin_priv = *coin_priv; + coin->h_age_commitment = ach; return GNUNET_OK; } @@ -240,6 +366,7 @@ TALER_planchet_to_coin ( void TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc, uint32_t kappa, + const struct TALER_RefreshMasterSecretP *rms, uint32_t num_new_coins, const struct TALER_RefreshCommitmentEntry *rcs, const struct TALER_CoinSpendPublicKeyP *coin_pub, @@ -248,6 +375,10 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc, struct GNUNET_HashContext *hash_context; hash_context = GNUNET_CRYPTO_hash_context_start (); + if (NULL != rms) + GNUNET_CRYPTO_hash_context_read (hash_context, + rms, + sizeof (*rms)); /* first, iterate over transfer public keys for hash_context */ for (unsigned int i = 0; i<kappa; i++) { @@ -259,7 +390,7 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc, hash_context */ for (unsigned int i = 0; i<num_new_coins; i++) { - struct TALER_DenominationHash denom_hash; + struct TALER_DenominationHashP denom_hash; /* The denomination keys should / must all be identical regardless of what offset we use, so we use [0]. */ @@ -294,9 +425,8 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc, { const struct TALER_RefreshCoinData *rcd = &rce->new_coins[j]; - GNUNET_CRYPTO_hash_context_read (hash_context, - rcd->coin_ev, - rcd->coin_ev_size); + TALER_blinded_planchet_hash_ (&rcd->blinded_planchet, + hash_context); } } @@ -307,22 +437,11 @@ TALER_refresh_get_commitment (struct TALER_RefreshCommitmentP *rc, void -TALER_coin_ev_hash (const void *coin_ev, - size_t coin_ev_size, - struct TALER_BlindedCoinHash *bch) -{ - GNUNET_CRYPTO_hash (coin_ev, - coin_ev_size, - &bch->hash); -} - - -void TALER_coin_pub_hash (const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_AgeHash *age_commitment_hash, - struct TALER_CoinPubHash *coin_h) + const struct TALER_AgeCommitmentHash *ach, + struct TALER_CoinPubHashP *coin_h) { - if (NULL == age_commitment_hash) + if (TALER_AgeCommitmentHash_isNullOrZero (ach)) { /* No age commitment was set */ GNUNET_CRYPTO_hash (&coin_pub->eddsa_pub, @@ -333,23 +452,93 @@ 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_AgeHash); - char data[key_s + age_s]; + struct GNUNET_HashContext *hash_context; - GNUNET_memcpy (&data[0], - &coin_pub->eddsa_pub, - key_s); + hash_context = GNUNET_CRYPTO_hash_context_start (); - GNUNET_memcpy (&data[key_s], - age_commitment_hash, - age_s); + GNUNET_CRYPTO_hash_context_read ( + hash_context, + &coin_pub->eddsa_pub, + sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey)); - GNUNET_CRYPTO_hash (&data, - key_s + age_s, - &coin_h->hash); + GNUNET_CRYPTO_hash_context_read ( + hash_context, + ach, + sizeof(struct TALER_AgeCommitmentHash)); + + GNUNET_CRYPTO_hash_context_finish ( + hash_context, + &coin_h->hash); } } +void +TALER_coin_ev_hash (const struct TALER_BlindedPlanchet *blinded_planchet, + const struct TALER_DenominationHashP *denom_hash, + struct TALER_BlindedCoinHashP *bch) +{ + struct GNUNET_HashContext *hash_context; + + hash_context = GNUNET_CRYPTO_hash_context_start (); + GNUNET_CRYPTO_hash_context_read (hash_context, + denom_hash, + sizeof(*denom_hash)); + TALER_blinded_planchet_hash_ (blinded_planchet, + hash_context); + GNUNET_CRYPTO_hash_context_finish (hash_context, + &bch->hash); +} + + +GNUNET_NETWORK_STRUCT_BEGIN +/** + * Structure we hash to compute the group key for + * a denomination group. + */ +struct DenominationGroupP +{ + /** + * Value of coins in this denomination group. + */ + struct TALER_AmountNBO value; + + /** + * Fee structure for all coins in the group. + */ + struct TALER_DenomFeeSetNBOP fees; + + /** + * Age mask for the denomiation, in NBO. + */ + uint32_t age_mask GNUNET_PACKED; + + /** + * Cipher used for the denomination, in NBO. + */ + uint32_t cipher GNUNET_PACKED; +}; +GNUNET_NETWORK_STRUCT_END + + +void +TALER_denomination_group_get_key ( + const struct TALER_DenominationGroup *dg, + struct GNUNET_HashCode *key) +{ + struct DenominationGroupP dgp = { + .age_mask = htonl (dg->age_mask.bits), + .cipher = htonl (dg->cipher) + }; + + TALER_amount_hton (&dgp.value, + &dg->value); + TALER_denom_fee_set_hton (&dgp.fees, + &dg->fees); + GNUNET_CRYPTO_hash (&dgp, + sizeof (dgp), + key); +} + + /* end of crypto.c */ diff --git a/src/util/crypto_confirmation.c b/src/util/crypto_confirmation.c new file mode 100644 index 000000000..99552f150 --- /dev/null +++ b/src/util/crypto_confirmation.c @@ -0,0 +1,293 @@ +/* + This file is part of TALER + Copyright (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file util/crypto_confirmation.c + * @brief confirmation computation + * @author Christian Grothoff + * @author Priscilla Huang + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_mhd_lib.h" +#include <gnunet/gnunet_db_lib.h> +#include <gcrypt.h> + +/** + * How long is a TOTP code valid? + */ +#define TOTP_VALIDITY_PERIOD GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * Range of time we allow (plus-minus). + */ +#define TIME_INTERVAL_RANGE 2 + + +/** + * Compute TOTP code at current time with offset + * @a time_off for the @a key. + * + * @param ts current time + * @param time_off offset to apply when computing the code + * @param key pos_key in binary + * @param key_size number of bytes in @a key + */ +static uint64_t +compute_totp (struct GNUNET_TIME_Timestamp ts, + int time_off, + const void *key, + size_t key_size) +{ + struct GNUNET_TIME_Absolute now; + time_t t; + uint64_t ctr; + uint8_t hmac[20]; /* SHA1: 20 bytes */ + + now = ts.abs_time; + while (time_off < 0) + { + now = GNUNET_TIME_absolute_subtract (now, + TOTP_VALIDITY_PERIOD); + time_off++; + } + while (time_off > 0) + { + now = GNUNET_TIME_absolute_add (now, + TOTP_VALIDITY_PERIOD); + time_off--; + } + t = now.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us; + ctr = GNUNET_htonll (t / 30LLU); + + { + gcry_md_hd_t md; + const unsigned char *mc; + + GNUNET_assert (GPG_ERR_NO_ERROR == + gcry_md_open (&md, + GCRY_MD_SHA1, + GCRY_MD_FLAG_HMAC)); + GNUNET_assert (GPG_ERR_NO_ERROR == + gcry_md_setkey (md, + key, + key_size)); + gcry_md_write (md, + &ctr, + sizeof (ctr)); + mc = gcry_md_read (md, + GCRY_MD_SHA1); + GNUNET_assert (NULL != mc); + GNUNET_memcpy (hmac, + mc, + sizeof (hmac)); + gcry_md_close (md); + } + + { + uint32_t code = 0; + int offset; + + offset = hmac[sizeof (hmac) - 1] & 0x0f; + for (int count = 0; count < 4; count++) + code |= ((uint32_t) hmac[offset + 3 - count]) << (8 * count); + code &= 0x7fffffff; + /* always use 8 digits (maximum) */ + code = code % 100000000; + return code; + } +} + + +int +TALER_rfc3548_base32decode (const char *val, + size_t val_size, + void *key, + size_t key_len) +{ + /** + * 32 characters for decoding, using RFC 3548. + */ + static const char *decTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + unsigned char *udata = key; + unsigned int wpos = 0; + unsigned int rpos = 0; + unsigned int bits = 0; + unsigned int vbit = 0; + + while ((rpos < val_size) || (vbit >= 8)) + { + if ((rpos < val_size) && (vbit < 8)) + { + char c = val[rpos++]; + + if (c == '=') + { + /* padding character */ + if (rpos == val_size) + break; /* Ok, 1x '=' padding is allowed */ + if ( ('=' == val[rpos]) && + (rpos + 1 == val_size) ) + break; /* Ok, 2x '=' padding is allowed */ + return -1; /* invalid padding */ + } + const char *p = strchr (decTable__, toupper (c)); + if (! p) + { + /* invalid character */ + return -1; + } + bits = (bits << 5) | (p - decTable__); + vbit += 5; + } + if (vbit >= 8) + { + udata[wpos++] = (bits >> (vbit - 8)) & 0xFF; + vbit -= 8; + } + } + return wpos; +} + + +/** + * @brief Builds POS confirmation to verify payment. + * + * @param h_key opaque key for the totp operation + * @param h_key_len size of h_key in bytes + * @param ts current time + * @return Token on success, NULL of failure + */ +static char * +executive_totp (void *h_key, + size_t h_key_len, + struct GNUNET_TIME_Timestamp ts) +{ + uint64_t code; /* totp code */ + char *ret; + ret = NULL; + + for (int i = -TIME_INTERVAL_RANGE; i<= TIME_INTERVAL_RANGE; i++) + { + code = compute_totp (ts, + i, + h_key, + h_key_len); + if (NULL == ret) + { + GNUNET_asprintf (&ret, + "%08llu", + (unsigned long long) code); + } + else + { + char *tmp; + + GNUNET_asprintf (&tmp, + "%s\n%08llu", + ret, + (unsigned long long) code); + GNUNET_free (ret); + ret = tmp; + } + } + return ret; + +} + + +char * +TALER_build_pos_confirmation (const char *pos_key, + enum TALER_MerchantConfirmationAlgorithm pos_alg, + const struct TALER_Amount *total, + struct GNUNET_TIME_Timestamp ts) +{ + size_t pos_key_length = strlen (pos_key); + void *key; /* pos_key in binary */ + size_t key_len; /* length of the key */ + char *ret; + int dret; + + if (TALER_MCA_NONE == pos_alg) + return NULL; + key_len = pos_key_length * 5 / 8; + key = GNUNET_malloc (key_len); + dret = TALER_rfc3548_base32decode (pos_key, + pos_key_length, + key, + key_len); + if (-1 == dret) + { + GNUNET_free (key); + GNUNET_break_op (0); + return NULL; + } + GNUNET_assert (dret <= key_len); + key_len = (size_t) dret; + switch (pos_alg) + { + case TALER_MCA_NONE: + GNUNET_break (0); + GNUNET_free (key); + return NULL; + case TALER_MCA_WITHOUT_PRICE: /* and 30s */ + /* Return all T-OTP codes in range separated by new lines, e.g. + "12345678 + 24522552 + 25262425 + 42543525 + 25253552" + */ + ret = executive_totp (key, + key_len, + ts); + GNUNET_free (key); + return ret; + case TALER_MCA_WITH_PRICE: + { + struct GNUNET_HashCode hkey; + struct TALER_AmountNBO ntotal; + + if ( (NULL == total) || + (GNUNET_YES != + TALER_amount_is_valid (total) ) ) + { + GNUNET_break_op (0); + return NULL; + } + TALER_amount_hton (&ntotal, + total); + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (&hkey, + sizeof (hkey), + &ntotal, + sizeof (ntotal), + key, + key_len, + NULL, + 0)); + GNUNET_free (key); + ret = executive_totp (&hkey, + sizeof(hkey), + ts); + GNUNET_free (key); + return ret; + } + } + GNUNET_free (key); + GNUNET_break (0); + return NULL; +} diff --git a/src/util/crypto_contract.c b/src/util/crypto_contract.c new file mode 100644 index 000000000..bec34c983 --- /dev/null +++ b/src/util/crypto_contract.c @@ -0,0 +1,661 @@ +/* + This file is part of TALER + Copyright (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/crypto_contract.c + * @brief functions for encrypting and decrypting contracts for P2P payments + * @author Christian Grothoff <christian@grothoff.org> + */ +#include "platform.h" +#include "taler_util.h" +#include <zlib.h> +#include "taler_exchange_service.h" + + +/** + * Different types of contracts supported. + */ +enum ContractFormats +{ + /** + * The encrypted contract represents a payment offer. The receiver + * can merge it into a reserve/account to accept the contract and + * obtain the payment. + */ + TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER = 0, + + /** + * The encrypted contract represents a payment request. + */ + TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST = 1 +}; + + +/** + * Nonce used for encryption, 24 bytes. + */ +struct NonceP +{ + uint8_t nonce[crypto_secretbox_NONCEBYTES]; +}; + +/** + * Specifies a key used for symmetric encryption, 32 bytes. + */ +struct SymKeyP +{ + uint32_t key[8]; +}; + + +/** + * Compute @a key. + * + * @param key_material key for calculation + * @param key_m_len length of key + * @param nonce nonce for calculation + * @param salt salt value for calculation + * @param[out] key where to write the en-/description key + */ +static void +derive_key (const void *key_material, + size_t key_m_len, + const struct NonceP *nonce, + const char *salt, + struct SymKeyP *key) +{ + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (key, + sizeof (*key), + /* salt / XTS */ + nonce, + sizeof (*nonce), + /* ikm */ + key_material, + key_m_len, + /* info chunks */ + /* The "salt" passed here is actually not something random, + but a protocol-specific identifier string. Thus + we pass it as a context info to the HKDF */ + salt, + strlen (salt), + NULL, + 0)); +} + + +/** + * Encryption of data. + * + * @param nonce value to use for the nonce + * @param key key which is used to derive a key/iv pair from + * @param key_len length of key + * @param data data to encrypt + * @param data_size size of the data + * @param salt salt value which is used for key derivation + * @param[out] res ciphertext output + * @param[out] res_size size of the ciphertext + */ +static void +blob_encrypt (const struct NonceP *nonce, + const void *key, + size_t key_len, + const void *data, + size_t data_size, + const char *salt, + void **res, + size_t *res_size) +{ + size_t ciphertext_size; + struct SymKeyP skey; + + derive_key (key, + key_len, + nonce, + salt, + &skey); + ciphertext_size = crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES + + data_size; + *res_size = ciphertext_size; + *res = GNUNET_malloc (ciphertext_size); + GNUNET_memcpy (*res, + nonce, + crypto_secretbox_NONCEBYTES); + GNUNET_assert (0 == + crypto_secretbox_easy (*res + crypto_secretbox_NONCEBYTES, + data, + data_size, + (void *) nonce, + (void *) &skey)); +} + + +/** + * Decryption of data like encrypted recovery document etc. + * + * @param key key which is used to derive a key/iv pair from + * @param key_len length of key + * @param data data to decrypt + * @param data_size size of the data + * @param salt salt value which is used for key derivation + * @param[out] res plaintext output + * @param[out] res_size size of the plaintext + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +blob_decrypt (const void *key, + size_t key_len, + const void *data, + size_t data_size, + const char *salt, + void **res, + size_t *res_size) +{ + const struct NonceP *nonce; + struct SymKeyP skey; + size_t plaintext_size; + + if (data_size < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + nonce = data; + derive_key (key, + key_len, + nonce, + salt, + &skey); + plaintext_size = data_size - (crypto_secretbox_NONCEBYTES + + crypto_secretbox_MACBYTES); + *res = GNUNET_malloc (plaintext_size); + *res_size = plaintext_size; + if (0 != crypto_secretbox_open_easy (*res, + data + crypto_secretbox_NONCEBYTES, + data_size - crypto_secretbox_NONCEBYTES, + (void *) nonce, + (void *) &skey)) + { + GNUNET_break (0); + GNUNET_free (*res); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Header for encrypted contracts. + */ +struct ContractHeaderP +{ + /** + * Type of the contract, in NBO. + */ + uint32_t ctype; + + /** + * Length of the encrypted contract, in NBO. + */ + uint32_t clen; +}; + + +/** + * Header for encrypted contracts. + */ +struct ContractHeaderMergeP +{ + /** + * Generic header. + */ + struct ContractHeaderP header; + + /** + * Private key with the merge capability. + */ + struct TALER_PurseMergePrivateKeyP merge_priv; +}; + + +/** + * Salt we use when encrypting contracts for merge. + */ +#define MERGE_SALT "p2p-merge-contract" + + +void +TALER_CRYPTO_contract_encrypt_for_merge ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_ContractDiffiePrivateP *contract_priv, + const struct TALER_PurseMergePrivateKeyP *merge_priv, + const json_t *contract_terms, + void **econtract, + size_t *econtract_size) +{ + struct GNUNET_HashCode key; + char *cstr; + size_t clen; + void *xbuf; + struct ContractHeaderMergeP *hdr; + struct NonceP nonce; + uLongf cbuf_size; + int ret; + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)); + cstr = json_dumps (contract_terms, + JSON_COMPACT | JSON_SORT_KEYS); + clen = strlen (cstr); + cbuf_size = compressBound (clen); + xbuf = GNUNET_malloc (cbuf_size); + ret = compress (xbuf, + &cbuf_size, + (const Bytef *) cstr, + clen); + GNUNET_assert (Z_OK == ret); + free (cstr); + hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size); + hdr->header.ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER); + hdr->header.clen = htonl ((uint32_t) clen); + hdr->merge_priv = *merge_priv; + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); + GNUNET_free (xbuf); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + &key, + sizeof (key), + hdr, + sizeof (*hdr) + cbuf_size, + MERGE_SALT, + econtract, + econtract_size); + GNUNET_free (hdr); +} + + +json_t * +TALER_CRYPTO_contract_decrypt_for_merge ( + const struct TALER_ContractDiffiePrivateP *contract_priv, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const void *econtract, + size_t econtract_size, + struct TALER_PurseMergePrivateKeyP *merge_priv) +{ + struct GNUNET_HashCode key; + void *xhdr; + size_t hdr_size; + const struct ContractHeaderMergeP *hdr; + char *cstr; + uLongf clen; + json_error_t json_error; + json_t *ret; + + if (GNUNET_OK != + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + blob_decrypt (&key, + sizeof (key), + econtract, + econtract_size, + MERGE_SALT, + &xhdr, + &hdr_size)) + { + GNUNET_break_op (0); + return NULL; + } + if (hdr_size < sizeof (*hdr)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + hdr = xhdr; + if (TALER_EXCHANGE_CONTRACT_PAYMENT_OFFER != ntohl (hdr->header.ctype)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + clen = ntohl (hdr->header.clen); + if (clen >= GNUNET_MAX_MALLOC_CHECKED) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + cstr = GNUNET_malloc (clen + 1); + if (Z_OK != + uncompress ((Bytef *) cstr, + &clen, + (const Bytef *) &hdr[1], + hdr_size - sizeof (*hdr))) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + GNUNET_free (xhdr); + return NULL; + } + *merge_priv = hdr->merge_priv; + GNUNET_free (xhdr); + ret = json_loadb ((char *) cstr, + clen, + JSON_DECODE_ANY, + &json_error); + if (NULL == ret) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + return NULL; + } + GNUNET_free (cstr); + return ret; +} + + +/** + * Salt we use when encrypting contracts for merge. + */ +#define DEPOSIT_SALT "p2p-deposit-contract" + + +void +TALER_CRYPTO_contract_encrypt_for_deposit ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_ContractDiffiePrivateP *contract_priv, + const json_t *contract_terms, + void **econtract, + size_t *econtract_size) +{ + struct GNUNET_HashCode key; + char *cstr; + size_t clen; + void *xbuf; + struct ContractHeaderP *hdr; + struct NonceP nonce; + uLongf cbuf_size; + int ret; + void *xecontract; + size_t xecontract_size; + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)); + cstr = json_dumps (contract_terms, + JSON_COMPACT | JSON_SORT_KEYS); + GNUNET_assert (NULL != cstr); + clen = strlen (cstr); + cbuf_size = compressBound (clen); + xbuf = GNUNET_malloc (cbuf_size); + ret = compress (xbuf, + &cbuf_size, + (const Bytef *) cstr, + clen); + GNUNET_assert (Z_OK == ret); + free (cstr); + hdr = GNUNET_malloc (sizeof (*hdr) + cbuf_size); + hdr->ctype = htonl (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST); + hdr->clen = htonl ((uint32_t) clen); + GNUNET_memcpy (&hdr[1], + xbuf, + cbuf_size); + GNUNET_free (xbuf); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + &key, + sizeof (key), + hdr, + sizeof (*hdr) + cbuf_size, + DEPOSIT_SALT, + &xecontract, + &xecontract_size); + GNUNET_free (hdr); + /* prepend purse_pub */ + *econtract = GNUNET_malloc (xecontract_size + sizeof (*purse_pub)); + GNUNET_memcpy (*econtract, + purse_pub, + sizeof (*purse_pub)); + GNUNET_memcpy (sizeof (*purse_pub) + *econtract, + xecontract, + xecontract_size); + *econtract_size = xecontract_size + sizeof (*purse_pub); + GNUNET_free (xecontract); +} + + +json_t * +TALER_CRYPTO_contract_decrypt_for_deposit ( + const struct TALER_ContractDiffiePrivateP *contract_priv, + const void *econtract, + size_t econtract_size) +{ + const struct TALER_PurseContractPublicKeyP *purse_pub = econtract; + + if (econtract_size < sizeof (*purse_pub)) + { + GNUNET_break_op (0); + return NULL; + } + struct GNUNET_HashCode key; + void *xhdr; + size_t hdr_size; + const struct ContractHeaderP *hdr; + char *cstr; + uLongf clen; + json_error_t json_error; + json_t *ret; + + if (GNUNET_OK != + GNUNET_CRYPTO_ecdh_eddsa (&contract_priv->ecdhe_priv, + &purse_pub->eddsa_pub, + &key)) + { + GNUNET_break (0); + return NULL; + } + econtract += sizeof (*purse_pub); + econtract_size -= sizeof (*purse_pub); + if (GNUNET_OK != + blob_decrypt (&key, + sizeof (key), + econtract, + econtract_size, + DEPOSIT_SALT, + &xhdr, + &hdr_size)) + { + GNUNET_break_op (0); + return NULL; + } + if (hdr_size < sizeof (*hdr)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + hdr = xhdr; + if (TALER_EXCHANGE_CONTRACT_PAYMENT_REQUEST != ntohl (hdr->ctype)) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + clen = ntohl (hdr->clen); + if (clen >= GNUNET_MAX_MALLOC_CHECKED) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + cstr = GNUNET_malloc (clen + 1); + if (Z_OK != + uncompress ((Bytef *) cstr, + &clen, + (const Bytef *) &hdr[1], + hdr_size - sizeof (*hdr))) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + GNUNET_free (xhdr); + return NULL; + } + GNUNET_free (xhdr); + ret = json_loadb ((char *) cstr, + clen, + JSON_DECODE_ANY, + &json_error); + if (NULL == ret) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + return NULL; + } + GNUNET_free (cstr); + return ret; +} + + +/** + * Salt we use when encrypting KYC attributes. + */ +#define ATTRIBUTE_SALT "kyc-attributes" + + +void +TALER_CRYPTO_kyc_attributes_encrypt ( + const struct TALER_AttributeEncryptionKeyP *key, + const json_t *attr, + void **enc_attr, + size_t *enc_attr_size) +{ + uLongf cbuf_size; + char *cstr; + uLongf clen; + void *xbuf; + int ret; + uint32_t belen; + struct NonceP nonce; + + cstr = json_dumps (attr, + JSON_COMPACT | JSON_SORT_KEYS); + GNUNET_assert (NULL != cstr); + clen = strlen (cstr); + GNUNET_assert (clen <= UINT32_MAX); + cbuf_size = compressBound (clen); + xbuf = GNUNET_malloc (cbuf_size + sizeof (uint32_t)); + belen = htonl ((uint32_t) clen); + GNUNET_memcpy (xbuf, + &belen, + sizeof (belen)); + ret = compress (xbuf + 4, + &cbuf_size, + (const Bytef *) cstr, + clen); + GNUNET_assert (Z_OK == ret); + free (cstr); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &nonce, + sizeof (nonce)); + blob_encrypt (&nonce, + key, + sizeof (*key), + xbuf, + cbuf_size + sizeof (uint32_t), + ATTRIBUTE_SALT, + enc_attr, + enc_attr_size); + GNUNET_free (xbuf); +} + + +json_t * +TALER_CRYPTO_kyc_attributes_decrypt ( + const struct TALER_AttributeEncryptionKeyP *key, + const void *enc_attr, + size_t enc_attr_size) +{ + void *xhdr; + size_t hdr_size; + char *cstr; + uLongf clen; + json_error_t json_error; + json_t *ret; + uint32_t belen; + + if (GNUNET_OK != + blob_decrypt (key, + sizeof (*key), + enc_attr, + enc_attr_size, + ATTRIBUTE_SALT, + &xhdr, + &hdr_size)) + { + GNUNET_break_op (0); + return NULL; + } + GNUNET_memcpy (&belen, + xhdr, + sizeof (belen)); + clen = ntohl (belen); + if (clen >= GNUNET_MAX_MALLOC_CHECKED) + { + GNUNET_break_op (0); + GNUNET_free (xhdr); + return NULL; + } + cstr = GNUNET_malloc (clen + 1); + if (Z_OK != + uncompress ((Bytef *) cstr, + &clen, + (const Bytef *) (xhdr + sizeof (uint32_t)), + hdr_size - sizeof (uint32_t))) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + GNUNET_free (xhdr); + return NULL; + } + GNUNET_free (xhdr); + ret = json_loadb ((char *) cstr, + clen, + JSON_DECODE_ANY, + &json_error); + if (NULL == ret) + { + GNUNET_break_op (0); + GNUNET_free (cstr); + return NULL; + } + GNUNET_free (cstr); + return ret; +} diff --git a/src/util/crypto_helper_cs.c b/src/util/crypto_helper_cs.c new file mode 100644 index 000000000..4c4a56feb --- /dev/null +++ b/src/util/crypto_helper_cs.c @@ -0,0 +1,1316 @@ +/* + This file is part of TALER + Copyright (C) 2020, 2021, 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/crypto_helper_cs.c + * @brief utility functions for running out-of-process private key operations + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler-exchange-secmod-cs.h" +#include <poll.h> +#include "crypto_helper_common.h" + + +struct TALER_CRYPTO_CsDenominationHelper +{ + /** + * Function to call with updates to available key material. + */ + TALER_CRYPTO_CsDenominationKeyStatusCallback dkc; + + /** + * Closure for @e dkc + */ + void *dkc_cls; + + /** + * Socket address of the denomination helper process. + * Used to reconnect if the connection breaks. + */ + struct sockaddr_un sa; + + /** + * The UNIX domain socket, -1 if we are currently not connected. + */ + int sock; + + /** + * Have we ever been sync'ed? + */ + bool synced; +}; + + +/** + * Disconnect from the helper process. Updates + * @e sock field in @a dh. + * + * @param[in,out] dh handle to tear down connection of + */ +static void +do_disconnect (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + GNUNET_break (0 == close (dh->sock)); + dh->sock = -1; + dh->synced = false; +} + + +/** + * Try to connect to the helper process. Updates + * @e sock field in @a dh. + * + * @param[in,out] dh handle to establish connection for + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +try_connect (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + if (-1 != dh->sock) + return GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Establishing connection!\n"); + dh->sock = socket (AF_UNIX, + SOCK_STREAM, + 0); + if (-1 == dh->sock) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "socket"); + return GNUNET_SYSERR; + } + if (0 != + connect (dh->sock, + (const struct sockaddr *) &dh->sa, + sizeof (dh->sa))) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "connect", + dh->sa.sun_path); + do_disconnect (dh); + return GNUNET_SYSERR; + } + TALER_CRYPTO_helper_cs_poll (dh); + return GNUNET_OK; +} + + +struct TALER_CRYPTO_CsDenominationHelper * +TALER_CRYPTO_helper_cs_connect ( + const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, + TALER_CRYPTO_CsDenominationKeyStatusCallback dkc, + void *dkc_cls) +{ + struct TALER_CRYPTO_CsDenominationHelper *dh; + char *unixpath; + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-cs", + section); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + secname, + "UNIXPATH", + &unixpath)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "UNIXPATH"); + GNUNET_free (secname); + return NULL; + } + /* we use >= here because we want the sun_path to always + be 0-terminated */ + if (strlen (unixpath) >= sizeof (dh->sa.sun_path)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + secname, + "UNIXPATH", + "path too long"); + GNUNET_free (unixpath); + GNUNET_free (secname); + return NULL; + } + GNUNET_free (secname); + dh = GNUNET_new (struct TALER_CRYPTO_CsDenominationHelper); + dh->dkc = dkc; + dh->dkc_cls = dkc_cls; + dh->sa.sun_family = AF_UNIX; + strncpy (dh->sa.sun_path, + unixpath, + sizeof (dh->sa.sun_path) - 1); + GNUNET_free (unixpath); + dh->sock = -1; + if (GNUNET_OK != + try_connect (dh)) + { + TALER_CRYPTO_helper_cs_disconnect (dh); + return NULL; + } + return dh; +} + + +/** + * Handle a #TALER_HELPER_CS_MT_AVAIL message from the helper. + * + * @param dh helper context + * @param hdr message that we received + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_mt_avail (struct TALER_CRYPTO_CsDenominationHelper *dh, + const struct GNUNET_MessageHeader *hdr) +{ + const struct TALER_CRYPTO_CsKeyAvailableNotification *kan + = (const struct TALER_CRYPTO_CsKeyAvailableNotification *) hdr; + const char *buf = (const char *) &kan[1]; + const char *section_name; + uint16_t snl; + + if (sizeof (*kan) > ntohs (hdr->size)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + snl = ntohs (kan->section_name_len); + if (ntohs (hdr->size) != sizeof (*kan) + snl) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (0 == snl) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + section_name = buf; + if ('\0' != section_name[snl - 1]) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + { + struct GNUNET_CRYPTO_BlindSignPublicKey *bsign_pub; + struct TALER_CsPubHashP h_cs; + + bsign_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); + bsign_pub->cipher = GNUNET_CRYPTO_BSA_CS; + bsign_pub->rc = 1; + bsign_pub->details.cs_public_key = kan->denom_pub; + + GNUNET_CRYPTO_hash (&bsign_pub->details.cs_public_key, + sizeof (bsign_pub->details.cs_public_key), + &bsign_pub->pub_key_hash); + h_cs.hash = bsign_pub->pub_key_hash; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received CS key %s (%s)\n", + GNUNET_h2s (&h_cs.hash), + section_name); + if (GNUNET_OK != + TALER_exchange_secmod_cs_verify ( + &h_cs, + section_name, + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), + GNUNET_TIME_relative_ntoh (kan->duration_withdraw), + &kan->secm_pub, + &kan->secm_sig)) + { + GNUNET_break_op (0); + GNUNET_CRYPTO_blind_sign_pub_decref (bsign_pub); + return GNUNET_SYSERR; + } + dh->dkc (dh->dkc_cls, + section_name, + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), + GNUNET_TIME_relative_ntoh (kan->duration_withdraw), + &h_cs, + bsign_pub, + &kan->secm_pub, + &kan->secm_sig); + GNUNET_CRYPTO_blind_sign_pub_decref (bsign_pub); + } + return GNUNET_OK; +} + + +/** + * Handle a #TALER_HELPER_CS_MT_PURGE message from the helper. + * + * @param dh helper context + * @param hdr message that we received + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_mt_purge (struct TALER_CRYPTO_CsDenominationHelper *dh, + const struct GNUNET_MessageHeader *hdr) +{ + const struct TALER_CRYPTO_CsKeyPurgeNotification *pn + = (const struct TALER_CRYPTO_CsKeyPurgeNotification *) hdr; + + if (sizeof (*pn) != ntohs (hdr->size)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received revocation of denomination key %s\n", + GNUNET_h2s (&pn->h_cs.hash)); + dh->dkc (dh->dkc_cls, + NULL, + GNUNET_TIME_UNIT_ZERO_TS, + GNUNET_TIME_UNIT_ZERO, + &pn->h_cs, + NULL, + NULL, + NULL); + return GNUNET_OK; +} + + +void +TALER_CRYPTO_helper_cs_poll (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + char buf[UINT16_MAX]; + size_t off = 0; + unsigned int retry_limit = 3; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + + if (GNUNET_OK != + try_connect (dh)) + return; /* give up */ + while (1) + { + uint16_t msize; + ssize_t ret; + + ret = recv (dh->sock, + buf + off, + sizeof (buf) - off, + (dh->synced && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (dh->synced); + GNUNET_assert (0 == off); + break; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + if (0 == retry_limit) + return; /* give up */ + if (GNUNET_OK != + try_connect (dh)) + return; /* give up */ + retry_limit--; + continue; + } + if (0 == ret) + { + GNUNET_break (0 == off); + return; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received message of type %u and length %u\n", + (unsigned int) ntohs (hdr->type), + (unsigned int) msize); + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_AVAIL: + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return; + } + break; + case TALER_HELPER_CS_MT_PURGE: + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return; + } + break; + case TALER_HELPER_CS_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Now synchronized with CS helper\n"); + dh->synced = true; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %d (len: %u)\n", + (unsigned int) ntohs (hdr->type), + (unsigned int) msize); + GNUNET_break_op (0); + do_disconnect (dh); + return; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } +} + + +enum TALER_ErrorCode +TALER_CRYPTO_helper_cs_sign ( + struct TALER_CRYPTO_CsDenominationHelper *dh, + const struct TALER_CRYPTO_CsSignRequest *req, + bool for_melt, + struct TALER_BlindedDenominationSignature *bs) +{ + enum TALER_ErrorCode ec = TALER_EC_INVALID; + const struct TALER_CsPubHashP *h_cs = req->h_cs; + + memset (bs, + 0, + sizeof (*bs)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting signature process\n"); + if (GNUNET_OK != + try_connect (dh)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to helper\n"); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting signature\n"); + { + char buf[sizeof (struct TALER_CRYPTO_CsSignRequestMessage)]; + struct TALER_CRYPTO_CsSignRequestMessage *sr + = (struct TALER_CRYPTO_CsSignRequestMessage *) buf; + + sr->header.size = htons (sizeof (buf)); + sr->header.type = htons (TALER_HELPER_CS_MT_REQ_SIGN); + sr->for_melt = htonl (for_melt ? 1 : 0); + sr->h_cs = *h_cs; + sr->message = *req->blinded_planchet; + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + buf, + sizeof (buf))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply\n"); + { + char buf[UINT16_MAX]; + size_t off = 0; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + bool finished = false; + + while (1) + { + uint16_t msize; + ssize_t ret; + + ret = recv (dh->sock, + &buf[off], + sizeof (buf) - off, + (finished && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (finished); + GNUNET_assert (0 == off); + return ec; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + break; + } + if (0 == ret) + { + GNUNET_break (0 == off); + if (! finished) + ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + return ec; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_RES_SIGNATURE: + if (msize != sizeof (struct TALER_CRYPTO_SignResponse)) + { + GNUNET_break_op (0); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + if (finished) + { + GNUNET_break_op (0); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + { + const struct TALER_CRYPTO_SignResponse *sr = + (const struct TALER_CRYPTO_SignResponse *) buf; + struct GNUNET_CRYPTO_BlindedSignature *blinded_sig; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received signature\n"); + ec = TALER_EC_NONE; + finished = true; + blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); + blinded_sig->cipher = GNUNET_CRYPTO_BSA_CS; + blinded_sig->rc = 1; + blinded_sig->details.blinded_cs_answer.b = ntohl (sr->b); + blinded_sig->details.blinded_cs_answer.s_scalar = sr->cs_answer; + bs->blinded_sig = blinded_sig; + break; + } + case TALER_HELPER_CS_MT_RES_SIGN_FAILURE: + if (msize != sizeof (struct TALER_CRYPTO_SignFailure)) + { + GNUNET_break_op (0); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + { + const struct TALER_CRYPTO_SignFailure *sf = + (const struct TALER_CRYPTO_SignFailure *) buf; + + ec = (enum TALER_ErrorCode) ntohl (sf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Signing failed with status %d!\n", + ec); + finished = true; + break; + } + case TALER_HELPER_CS_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Synchronized add odd time with CS helper!\n"); + dh->synced = true; + break; + default: + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %u\n", + ntohs (hdr->type)); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + goto end; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } /* while(1) */ +end: + if (finished) + TALER_blinded_denom_sig_free (bs); + return ec; + } +} + + +void +TALER_CRYPTO_helper_cs_revoke ( + struct TALER_CRYPTO_CsDenominationHelper *dh, + const struct TALER_CsPubHashP *h_cs) +{ + struct TALER_CRYPTO_CsRevokeRequest rr = { + .header.size = htons (sizeof (rr)), + .header.type = htons (TALER_HELPER_CS_MT_REQ_REVOKE), + .h_cs = *h_cs + }; + + if (GNUNET_OK != + try_connect (dh)) + return; /* give up */ + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + &rr, + sizeof (rr))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requested revocation of denomination key %s\n", + GNUNET_h2s (&h_cs->hash)); +} + + +enum TALER_ErrorCode +TALER_CRYPTO_helper_cs_r_derive (struct TALER_CRYPTO_CsDenominationHelper *dh, + const struct TALER_CRYPTO_CsDeriveRequest *cdr, + bool for_melt, + struct GNUNET_CRYPTO_CSPublicRPairP *crp) +{ + enum TALER_ErrorCode ec = TALER_EC_INVALID; + const struct TALER_CsPubHashP *h_cs = cdr->h_cs; + const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdr->nonce; + + memset (crp, + 0, + sizeof (*crp)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting R derivation process\n"); + if (GNUNET_OK != + try_connect (dh)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to helper\n"); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting R\n"); + { + struct TALER_CRYPTO_CsRDeriveRequest rdr = { + .header.size = htons (sizeof (rdr)), + .header.type = htons (TALER_HELPER_CS_MT_REQ_RDERIVE), + .for_melt = htonl (for_melt ? 1 : 0), + .h_cs = *h_cs, + .nonce = *nonce + }; + + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + &rdr, + sizeof (rdr))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply\n"); + { + char buf[UINT16_MAX]; + size_t off = 0; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + bool finished = false; + + while (1) + { + uint16_t msize; + ssize_t ret; + + ret = recv (dh->sock, + &buf[off], + sizeof (buf) - off, + (finished && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (finished); + GNUNET_assert (0 == off); + return ec; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + if (0 == ret) + { + GNUNET_break (0 == off); + if (! finished) + return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + return ec; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_RES_RDERIVE: + if (msize != sizeof (struct TALER_CRYPTO_RDeriveResponse)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + if (finished) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_RDeriveResponse *rdr = + (const struct TALER_CRYPTO_RDeriveResponse *) buf; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received R\n"); + finished = true; + ec = TALER_EC_NONE; + *crp = rdr->r_pub; + break; + } + case TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE: + if (msize != sizeof (struct TALER_CRYPTO_RDeriveFailure)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_RDeriveFailure *rdf = + (const struct TALER_CRYPTO_RDeriveFailure *) buf; + + ec = (enum TALER_ErrorCode) ntohl (rdf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "R derivation failed!\n"); + finished = true; + break; + } + case TALER_HELPER_CS_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Synchronized add odd time with CS helper!\n"); + dh->synced = true; + break; + default: + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %u\n", + ntohs (hdr->type)); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } /* while(1) */ + } +} + + +enum TALER_ErrorCode +TALER_CRYPTO_helper_cs_batch_sign ( + struct TALER_CRYPTO_CsDenominationHelper *dh, + unsigned int reqs_length, + const struct TALER_CRYPTO_CsSignRequest reqs[static reqs_length], + bool for_melt, + struct TALER_BlindedDenominationSignature bss[static reqs_length]) +{ + enum TALER_ErrorCode ec = TALER_EC_INVALID; + unsigned int rpos; + unsigned int rend; + unsigned int wpos; + + memset (bss, + 0, + sizeof (*bss) * reqs_length); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting signature process\n"); + if (GNUNET_OK != + try_connect (dh)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to helper\n"); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting %u signatures\n", + reqs_length); + rpos = 0; + rend = 0; + wpos = 0; + while (rpos < reqs_length) + { + unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchSignRequest); + + while ( (rend < reqs_length) && + (mlen + sizeof (struct TALER_CRYPTO_CsSignRequestMessage) + < UINT16_MAX) ) + { + mlen += sizeof (struct TALER_CRYPTO_CsSignRequestMessage); + rend++; + } + { + char obuf[mlen] GNUNET_ALIGN; + struct TALER_CRYPTO_BatchSignRequest *bsr + = (struct TALER_CRYPTO_BatchSignRequest *) obuf; + void *wbuf; + + bsr->header.type = htons (TALER_HELPER_CS_MT_REQ_BATCH_SIGN); + bsr->header.size = htons (mlen); + bsr->batch_size = htonl (rend - rpos); + wbuf = &bsr[1]; + for (unsigned int i = rpos; i<rend; i++) + { + struct TALER_CRYPTO_CsSignRequestMessage *csm = wbuf; + const struct TALER_CRYPTO_CsSignRequest *csr = &reqs[i]; + + csm->header.size = htons (sizeof (*csm)); + csm->header.type = htons (TALER_HELPER_CS_MT_REQ_SIGN); + csm->for_melt = htonl (for_melt ? 1 : 0); + csm->h_cs = *csr->h_cs; + csm->message = *csr->blinded_planchet; + wbuf += sizeof (*csm); + } + GNUNET_assert (wbuf == &obuf[mlen]); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending batch request [%u-%u)\n", + rpos, + rend); + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + obuf, + sizeof (obuf))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + } /* end of obuf scope */ + rpos = rend; + { + char buf[UINT16_MAX]; + size_t off = 0; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + bool finished = false; + + while (1) + { + uint16_t msize; + ssize_t ret; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply at %u (up to %u)\n", + wpos, + rend); + ret = recv (dh->sock, + &buf[off], + sizeof (buf) - off, + (finished && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (finished); + GNUNET_assert (0 == off); + break; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + if (0 == ret) + { + GNUNET_break (0 == off); + if (! finished) + return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + if (TALER_EC_NONE == ec) + break; + return ec; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_RES_SIGNATURE: + if (msize != sizeof (struct TALER_CRYPTO_SignResponse)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + if (finished) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_SignResponse *sr = + (const struct TALER_CRYPTO_SignResponse *) buf; + struct GNUNET_CRYPTO_BlindedSignature *blinded_sig; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u signature\n", + wpos); + blinded_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); + blinded_sig->cipher = GNUNET_CRYPTO_BSA_CS; + blinded_sig->rc = 1; + blinded_sig->details.blinded_cs_answer.b = ntohl (sr->b); + blinded_sig->details.blinded_cs_answer.s_scalar = sr->cs_answer; + + bss[wpos].blinded_sig = blinded_sig; + wpos++; + if (wpos == rend) + { + if (TALER_EC_INVALID == ec) + ec = TALER_EC_NONE; + finished = true; + } + break; + } + + case TALER_HELPER_CS_MT_RES_SIGN_FAILURE: + if (msize != sizeof (struct TALER_CRYPTO_SignFailure)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_SignFailure *sf = + (const struct TALER_CRYPTO_SignFailure *) buf; + + ec = (enum TALER_ErrorCode) ntohl (sf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Signing %u failed with status %d!\n", + wpos, + ec); + wpos++; + if (wpos == rend) + { + finished = true; + } + break; + } + case TALER_HELPER_CS_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Synchronized add odd time with CS helper!\n"); + dh->synced = true; + break; + default: + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %u\n", + ntohs (hdr->type)); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } /* while(1) */ + } /* scope */ + } /* while (rpos < cdrs_length) */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Existing with %u signatures and status %d\n", + wpos, + ec); + return ec; +} + + +enum TALER_ErrorCode +TALER_CRYPTO_helper_cs_r_batch_derive ( + struct TALER_CRYPTO_CsDenominationHelper *dh, + unsigned int cdrs_length, + const struct TALER_CRYPTO_CsDeriveRequest cdrs[static cdrs_length], + bool for_melt, + struct GNUNET_CRYPTO_CSPublicRPairP crps[static cdrs_length]) +{ + enum TALER_ErrorCode ec = TALER_EC_INVALID; + unsigned int rpos; + unsigned int rend; + unsigned int wpos; + + memset (crps, + 0, + sizeof (*crps) * cdrs_length); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting R derivation process\n"); + if (GNUNET_OK != + try_connect (dh)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to helper\n"); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting %u R pairs\n", + cdrs_length); + rpos = 0; + rend = 0; + wpos = 0; + while (rpos < cdrs_length) + { + unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchDeriveRequest); + + while ( (rend < cdrs_length) && + (mlen + sizeof (struct TALER_CRYPTO_CsRDeriveRequest) + < UINT16_MAX) ) + { + mlen += sizeof (struct TALER_CRYPTO_CsRDeriveRequest); + rend++; + } + { + char obuf[mlen] GNUNET_ALIGN; + struct TALER_CRYPTO_BatchDeriveRequest *bdr + = (struct TALER_CRYPTO_BatchDeriveRequest *) obuf; + void *wbuf; + + bdr->header.type = htons (TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE); + bdr->header.size = htons (mlen); + bdr->batch_size = htonl (rend - rpos); + wbuf = &bdr[1]; + for (unsigned int i = rpos; i<rend; i++) + { + struct TALER_CRYPTO_CsRDeriveRequest *rdr = wbuf; + const struct TALER_CRYPTO_CsDeriveRequest *cdr = &cdrs[i]; + + rdr->header.size = htons (sizeof (*rdr)); + rdr->header.type = htons (TALER_HELPER_CS_MT_REQ_RDERIVE); + rdr->for_melt = htonl (for_melt ? 1 : 0); + rdr->h_cs = *cdr->h_cs; + rdr->nonce = *cdr->nonce; + wbuf += sizeof (*rdr); + } + GNUNET_assert (wbuf == &obuf[mlen]); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending batch request [%u-%u)\n", + rpos, + rend); + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + obuf, + sizeof (obuf))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + } /* end of obuf scope */ + rpos = rend; + { + char buf[UINT16_MAX]; + size_t off = 0; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + bool finished = false; + + while (1) + { + uint16_t msize; + ssize_t ret; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply at %u (up to %u)\n", + wpos, + rend); + ret = recv (dh->sock, + &buf[off], + sizeof (buf) - off, + (finished && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (finished); + GNUNET_assert (0 == off); + break; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + if (0 == ret) + { + GNUNET_break (0 == off); + if (! finished) + return TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + if (TALER_EC_NONE == ec) + break; + return ec; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_RES_RDERIVE: + if (msize != sizeof (struct TALER_CRYPTO_RDeriveResponse)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + if (finished) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_RDeriveResponse *rdr = + (const struct TALER_CRYPTO_RDeriveResponse *) buf; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u R pair\n", + wpos); + crps[wpos] = rdr->r_pub; + wpos++; + if (wpos == rend) + { + if (TALER_EC_INVALID == ec) + ec = TALER_EC_NONE; + finished = true; + } + break; + } + case TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE: + if (msize != sizeof (struct TALER_CRYPTO_RDeriveFailure)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_RDeriveFailure *rdf = + (const struct TALER_CRYPTO_RDeriveFailure *) buf; + + ec = (enum TALER_ErrorCode) ntohl (rdf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "R derivation %u failed with status %d!\n", + wpos, + ec); + wpos++; + if (wpos == rend) + { + finished = true; + } + break; + } + case TALER_HELPER_CS_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_CS_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Synchronized add odd time with CS helper!\n"); + dh->synced = true; + break; + default: + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %u\n", + ntohs (hdr->type)); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } /* while(1) */ + } /* scope */ + } /* while (rpos < cdrs_length) */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Existing with %u signatures and status %d\n", + wpos, + ec); + return ec; +} + + +void +TALER_CRYPTO_helper_cs_disconnect ( + struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + if (-1 != dh->sock) + do_disconnect (dh); + GNUNET_free (dh); +} + + +/* end of crypto_helper_cs.c */ diff --git a/src/util/crypto_helper_esign.c b/src/util/crypto_helper_esign.c index 702ea74df..e044d31d1 100644 --- a/src/util/crypto_helper_esign.c +++ b/src/util/crypto_helper_esign.c @@ -111,21 +111,28 @@ try_connect (struct TALER_CRYPTO_ExchangeSignHelper *esh) struct TALER_CRYPTO_ExchangeSignHelper * TALER_CRYPTO_helper_esign_connect ( const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, TALER_CRYPTO_ExchangeKeyStatusCallback ekc, void *ekc_cls) { struct TALER_CRYPTO_ExchangeSignHelper *esh; char *unixpath; + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-exchange-secmod-eddsa", + secname, "UNIXPATH", &unixpath)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "UNIXPATH"); + GNUNET_free (secname); return NULL; } /* we use >= here because we want the sun_path to always @@ -133,12 +140,14 @@ TALER_CRYPTO_helper_esign_connect ( if (strlen (unixpath) >= sizeof (esh->sa.sun_path)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "UNIXPATH", "path too long"); GNUNET_free (unixpath); + GNUNET_free (secname); return NULL; } + GNUNET_free (secname); esh = GNUNET_new (struct TALER_CRYPTO_ExchangeSignHelper); esh->ekc = ekc; esh->ekc_cls = ekc_cls; @@ -182,7 +191,7 @@ handle_mt_avail (struct TALER_CRYPTO_ExchangeSignHelper *esh, if (GNUNET_OK != TALER_exchange_secmod_eddsa_verify ( &kan->exchange_pub, - GNUNET_TIME_absolute_ntoh (kan->anchor_time), + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration), &kan->secm_pub, &kan->secm_sig)) @@ -191,7 +200,7 @@ handle_mt_avail (struct TALER_CRYPTO_ExchangeSignHelper *esh, return GNUNET_SYSERR; } esh->ekc (esh->ekc_cls, - GNUNET_TIME_absolute_ntoh (kan->anchor_time), + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration), &kan->exchange_pub, &kan->secm_pub, @@ -220,7 +229,7 @@ handle_mt_purge (struct TALER_CRYPTO_ExchangeSignHelper *esh, return GNUNET_SYSERR; } esh->ekc (esh->ekc_cls, - GNUNET_TIME_UNIT_ZERO_ABS, + GNUNET_TIME_UNIT_ZERO_TS, GNUNET_TIME_UNIT_ZERO, &pn->exchange_pub, NULL, @@ -357,9 +366,9 @@ TALER_CRYPTO_helper_esign_sign_ ( sr->header.size = htons (sizeof (buf)); sr->header.type = htons (TALER_HELPER_EDDSA_MT_REQ_SIGN); sr->reserved = htonl (0); - memcpy (&sr->purpose, - purpose, - purpose_size); + GNUNET_memcpy (&sr->purpose, + purpose, + purpose_size); if (GNUNET_OK != TALER_crypto_helper_send_all (esh->sock, buf, diff --git a/src/util/crypto_helper_rsa.c b/src/util/crypto_helper_rsa.c index 85741d5e5..e23e12a88 100644 --- a/src/util/crypto_helper_rsa.c +++ b/src/util/crypto_helper_rsa.c @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ /** - * @file util/crypto_helper_denom.c + * @file util/crypto_helper_rsa.c * @brief utility functions for running out-of-process private key operations * @author Christian Grothoff */ @@ -83,6 +83,8 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) { if (-1 != dh->sock) return GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Establishing connection!\n"); dh->sock = socket (AF_UNIX, SOCK_STREAM, 0); @@ -103,6 +105,7 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) do_disconnect (dh); return GNUNET_SYSERR; } + TALER_CRYPTO_helper_rsa_poll (dh); return GNUNET_OK; } @@ -110,21 +113,28 @@ try_connect (struct TALER_CRYPTO_RsaDenominationHelper *dh) struct TALER_CRYPTO_RsaDenominationHelper * TALER_CRYPTO_helper_rsa_connect ( const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *section, TALER_CRYPTO_RsaDenominationKeyStatusCallback dkc, void *dkc_cls) { struct TALER_CRYPTO_RsaDenominationHelper *dh; char *unixpath; + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-rsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-exchange-secmod-rsa", + secname, "UNIXPATH", &unixpath)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + secname, "UNIXPATH"); + GNUNET_free (secname); return NULL; } /* we use >= here because we want the sun_path to always @@ -132,12 +142,14 @@ TALER_CRYPTO_helper_rsa_connect ( if (strlen (unixpath) >= sizeof (dh->sa.sun_path)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + secname, "UNIXPATH", "path too long"); GNUNET_free (unixpath); + GNUNET_free (secname); return NULL; } + GNUNET_free (secname); dh = GNUNET_new (struct TALER_CRYPTO_RsaDenominationHelper); dh->dkc = dkc; dh->dkc_cls = dkc_cls; @@ -153,7 +165,6 @@ TALER_CRYPTO_helper_rsa_connect ( TALER_CRYPTO_helper_rsa_disconnect (dh); return NULL; } - TALER_CRYPTO_helper_rsa_poll (dh); return dh; } @@ -201,46 +212,50 @@ handle_mt_avail (struct TALER_CRYPTO_RsaDenominationHelper *dh, } { - struct TALER_DenominationPublicKey denom_pub; + struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub; struct TALER_RsaPubHashP h_rsa; - denom_pub.cipher = TALER_DENOMINATION_RSA; - denom_pub.details.rsa_public_key + bs_pub = GNUNET_new (struct GNUNET_CRYPTO_BlindSignPublicKey); + bs_pub->cipher = GNUNET_CRYPTO_BSA_RSA; + bs_pub->details.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf, ntohs (kan->pub_size)); - if (NULL == denom_pub.details.rsa_public_key) + if (NULL == bs_pub->details.rsa_public_key) { GNUNET_break_op (0); + GNUNET_free (bs_pub); return GNUNET_SYSERR; } - GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.details.rsa_public_key, - &h_rsa.hash); + bs_pub->rc = 1; + GNUNET_CRYPTO_rsa_public_key_hash (bs_pub->details.rsa_public_key, + &bs_pub->pub_key_hash); + h_rsa.hash = bs_pub->pub_key_hash; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received RSA key %s (%s)\n", - GNUNET_h2s (&h_rsa.hash), + GNUNET_h2s (&bs_pub->pub_key_hash), section_name); if (GNUNET_OK != TALER_exchange_secmod_rsa_verify ( &h_rsa, section_name, - GNUNET_TIME_absolute_ntoh (kan->anchor_time), + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration_withdraw), &kan->secm_pub, &kan->secm_sig)) { GNUNET_break_op (0); - TALER_denom_pub_free (&denom_pub); + GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub); return GNUNET_SYSERR; } dh->dkc (dh->dkc_cls, section_name, - GNUNET_TIME_absolute_ntoh (kan->anchor_time), + GNUNET_TIME_timestamp_ntoh (kan->anchor_time), GNUNET_TIME_relative_ntoh (kan->duration_withdraw), &h_rsa, - &denom_pub, + bs_pub, &kan->secm_pub, &kan->secm_sig); - TALER_denom_pub_free (&denom_pub); + GNUNET_CRYPTO_blind_sign_pub_decref (bs_pub); } return GNUNET_OK; } @@ -270,7 +285,7 @@ handle_mt_purge (struct TALER_CRYPTO_RsaDenominationHelper *dh, GNUNET_h2s (&pn->h_rsa.hash)); dh->dkc (dh->dkc_cls, NULL, - GNUNET_TIME_UNIT_ZERO_ABS, + GNUNET_TIME_UNIT_ZERO_TS, GNUNET_TIME_UNIT_ZERO, &pn->h_rsa, NULL, @@ -385,39 +400,41 @@ more: } -struct TALER_BlindedDenominationSignature +enum TALER_ErrorCode TALER_CRYPTO_helper_rsa_sign ( struct TALER_CRYPTO_RsaDenominationHelper *dh, - const struct TALER_RsaPubHashP *h_rsa, - const void *msg, - size_t msg_size, - enum TALER_ErrorCode *ec) + const struct TALER_CRYPTO_RsaSignRequest *rsr, + struct TALER_BlindedDenominationSignature *bs) { - struct TALER_BlindedDenominationSignature ds = { - .cipher = TALER_DENOMINATION_INVALID - }; + enum TALER_ErrorCode ec = TALER_EC_INVALID; + memset (bs, + 0, + sizeof (*bs)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting signature process\n"); if (GNUNET_OK != try_connect (dh)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to connect to helper\n"); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; - return ds; + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting signature\n"); { - char buf[sizeof (struct TALER_CRYPTO_SignRequest) + msg_size]; + char buf[sizeof (struct TALER_CRYPTO_SignRequest) + rsr->msg_size]; struct TALER_CRYPTO_SignRequest *sr = (struct TALER_CRYPTO_SignRequest *) buf; sr->header.size = htons (sizeof (buf)); sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN); sr->reserved = htonl (0); - sr->h_rsa = *h_rsa; - memcpy (&sr[1], - msg, - msg_size); + sr->h_rsa = *rsr->h_rsa; + GNUNET_memcpy (&sr[1], + rsr->msg, + rsr->msg_size); if (GNUNET_OK != TALER_crypto_helper_send_all (dh->sock, buf, @@ -426,11 +443,12 @@ TALER_CRYPTO_helper_rsa_sign ( GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "send"); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; - return ds; + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; } } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply\n"); { char buf[UINT16_MAX]; size_t off = 0; @@ -438,7 +456,6 @@ TALER_CRYPTO_helper_rsa_sign ( = (const struct GNUNET_MessageHeader *) buf; bool finished = false; - *ec = TALER_EC_INVALID; while (1) { uint16_t msize; @@ -458,20 +475,20 @@ TALER_CRYPTO_helper_rsa_sign ( { GNUNET_assert (finished); GNUNET_assert (0 == off); - return ds; + return ec; } GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "recv"); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; break; } if (0 == ret) { GNUNET_break (0 == off); if (! finished) - *ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; - return ds; + ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + return ec; } off += ret; more: @@ -487,20 +504,21 @@ more: { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } if (finished) { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } { const struct TALER_CRYPTO_SignResponse *sr = (const struct TALER_CRYPTO_SignResponse *) buf; struct GNUNET_CRYPTO_RsaSignature *rsa_signature; + struct GNUNET_CRYPTO_BlindedSignature *blind_sig; rsa_signature = GNUNET_CRYPTO_rsa_signature_decode ( &sr[1], @@ -509,13 +527,18 @@ more: { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } - *ec = TALER_EC_NONE; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received signature\n"); + ec = TALER_EC_NONE; finished = true; - ds.cipher = TALER_DENOMINATION_RSA; - ds.details.blinded_rsa_signature = rsa_signature; + blind_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); + blind_sig->cipher = GNUNET_CRYPTO_BSA_RSA; + blind_sig->rc = 1; + blind_sig->details.blinded_rsa_signature = rsa_signature; + bs->blinded_sig = blind_sig; break; } case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE: @@ -523,36 +546,42 @@ more: { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } { const struct TALER_CRYPTO_SignFailure *sf = (const struct TALER_CRYPTO_SignFailure *) buf; - *ec = (enum TALER_ErrorCode) ntohl (sf->ec); + ec = (enum TALER_ErrorCode) ntohl (sf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Signing failed!\n"); finished = true; break; } case TALER_HELPER_RSA_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); if (GNUNET_OK != handle_mt_avail (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } break; /* while(1) loop ensures we recvfrom() again */ case TALER_HELPER_RSA_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); if (GNUNET_OK != handle_mt_purge (dh, hdr)) { GNUNET_break_op (0); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } break; /* while(1) loop ensures we recvfrom() again */ @@ -567,7 +596,7 @@ more: "Received unexpected message of type %u\n", ntohs (hdr->type)); do_disconnect (dh); - *ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; goto end; } memmove (buf, @@ -578,12 +607,272 @@ more: } /* while(1) */ end: if (finished) - TALER_blinded_denom_sig_free (&ds); - return ds; + TALER_blinded_denom_sig_free (bs); + return ec; } } +enum TALER_ErrorCode +TALER_CRYPTO_helper_rsa_batch_sign ( + struct TALER_CRYPTO_RsaDenominationHelper *dh, + unsigned int rsrs_length, + const struct TALER_CRYPTO_RsaSignRequest rsrs[static rsrs_length], + struct TALER_BlindedDenominationSignature bss[static rsrs_length]) +{ + enum TALER_ErrorCode ec = TALER_EC_INVALID; + unsigned int rpos; + unsigned int rend; + unsigned int wpos; + + memset (bss, + 0, + sizeof (*bss) * rsrs_length); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting signature process\n"); + if (GNUNET_OK != + try_connect (dh)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to connect to helper\n"); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting %u signatures\n", + rsrs_length); + rpos = 0; + rend = 0; + wpos = 0; + while (rpos < rsrs_length) + { + unsigned int mlen = sizeof (struct TALER_CRYPTO_BatchSignRequest); + + while ( (rend < rsrs_length) && + (mlen + + sizeof (struct TALER_CRYPTO_SignRequest) + + rsrs[rend].msg_size < UINT16_MAX) ) + { + mlen += sizeof (struct TALER_CRYPTO_SignRequest) + rsrs[rend].msg_size; + rend++; + } + { + char obuf[mlen] GNUNET_ALIGN; + struct TALER_CRYPTO_BatchSignRequest *bsr + = (struct TALER_CRYPTO_BatchSignRequest *) obuf; + void *wbuf; + + bsr->header.type = htons (TALER_HELPER_RSA_MT_REQ_BATCH_SIGN); + bsr->header.size = htons (mlen); + bsr->batch_size = htonl (rend - rpos); + wbuf = &bsr[1]; + for (unsigned int i = rpos; i<rend; i++) + { + struct TALER_CRYPTO_SignRequest *sr = wbuf; + const struct TALER_CRYPTO_RsaSignRequest *rsr = &rsrs[i]; + + sr->header.type = htons (TALER_HELPER_RSA_MT_REQ_SIGN); + sr->header.size = htons (sizeof (*sr) + rsr->msg_size); + sr->reserved = htonl (0); + sr->h_rsa = *rsr->h_rsa; + GNUNET_memcpy (&sr[1], + rsr->msg, + rsr->msg_size); + wbuf += sizeof (*sr) + rsr->msg_size; + } + GNUNET_assert (wbuf == &obuf[mlen]); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending batch request [%u-%u)\n", + rpos, + rend); + if (GNUNET_OK != + TALER_crypto_helper_send_all (dh->sock, + obuf, + sizeof (obuf))) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "send"); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + } + } + rpos = rend; + { + char buf[UINT16_MAX]; + size_t off = 0; + const struct GNUNET_MessageHeader *hdr + = (const struct GNUNET_MessageHeader *) buf; + bool finished = false; + + while (1) + { + uint16_t msize; + ssize_t ret; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Awaiting reply at %u (up to %u)\n", + wpos, + rend); + ret = recv (dh->sock, + &buf[off], + sizeof (buf) - off, + (finished && (0 == off)) + ? MSG_DONTWAIT + : 0); + if (ret < 0) + { + if (EINTR == errno) + continue; + if (EAGAIN == errno) + { + GNUNET_assert (finished); + GNUNET_assert (0 == off); + break; + } + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "recv"); + do_disconnect (dh); + ec = TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE; + break; + } + if (0 == ret) + { + GNUNET_break (0 == off); + if (! finished) + ec = TALER_EC_EXCHANGE_SIGNKEY_HELPER_BUG; + if (TALER_EC_NONE == ec) + break; + return ec; + } + off += ret; +more: + if (off < sizeof (struct GNUNET_MessageHeader)) + continue; + msize = ntohs (hdr->size); + if (off < msize) + continue; + switch (ntohs (hdr->type)) + { + case TALER_HELPER_RSA_MT_RES_SIGNATURE: + if (msize < sizeof (struct TALER_CRYPTO_SignResponse)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + if (finished) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_SignResponse *sr = + (const struct TALER_CRYPTO_SignResponse *) buf; + struct GNUNET_CRYPTO_RsaSignature *rsa_signature; + struct GNUNET_CRYPTO_BlindedSignature *blind_sig; + + rsa_signature = GNUNET_CRYPTO_rsa_signature_decode ( + &sr[1], + msize - sizeof (*sr)); + if (NULL == rsa_signature) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received %u signature\n", + wpos); + blind_sig = GNUNET_new (struct GNUNET_CRYPTO_BlindedSignature); + blind_sig->cipher = GNUNET_CRYPTO_BSA_RSA; + blind_sig->rc = 1; + blind_sig->details.blinded_rsa_signature = rsa_signature; + bss[wpos].blinded_sig = blind_sig; + wpos++; + if (wpos == rend) + { + if (TALER_EC_INVALID == ec) + ec = TALER_EC_NONE; + finished = true; + } + break; + } + case TALER_HELPER_RSA_MT_RES_SIGN_FAILURE: + if (msize != sizeof (struct TALER_CRYPTO_SignFailure)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + { + const struct TALER_CRYPTO_SignFailure *sf = + (const struct TALER_CRYPTO_SignFailure *) buf; + + ec = (enum TALER_ErrorCode) ntohl (sf->ec); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Signing %u failed with status %d!\n", + wpos, + ec); + wpos++; + if (wpos == rend) + { + finished = true; + } + break; + } + case TALER_HELPER_RSA_MT_AVAIL: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received new key!\n"); + if (GNUNET_OK != + handle_mt_avail (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_RSA_MT_PURGE: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received revocation!\n"); + if (GNUNET_OK != + handle_mt_purge (dh, + hdr)) + { + GNUNET_break_op (0); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + break; /* while(1) loop ensures we recvfrom() again */ + case TALER_HELPER_RSA_SYNCED: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Synchronized add odd time with RSA helper!\n"); + dh->synced = true; + break; + default: + GNUNET_break_op (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Received unexpected message of type %u\n", + ntohs (hdr->type)); + do_disconnect (dh); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_BUG; + } + memmove (buf, + &buf[msize], + off - msize); + off -= msize; + goto more; + } /* while(1) */ + } /* scope */ + } /* while (rpos < rsrs_length) */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Existing with %u signatures and status %d\n", + wpos, + ec); + return ec; +} + + void TALER_CRYPTO_helper_rsa_revoke ( struct TALER_CRYPTO_RsaDenominationHelper *dh, diff --git a/src/util/crypto_wire.c b/src/util/crypto_wire.c index e1c7d9646..aa504b81e 100644 --- a/src/util/crypto_wire.c +++ b/src/util/crypto_wire.c @@ -23,49 +23,10 @@ #include "taler_signatures.h" -enum GNUNET_GenericReturnValue -TALER_exchange_wire_signature_check ( - const char *payto_uri, - const struct TALER_MasterPublicKeyP *master_pub, - const struct TALER_MasterSignatureP *master_sig) -{ - struct TALER_MasterWireDetailsPS wd = { - .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS), - .purpose.size = htonl (sizeof (wd)) - }; - - TALER_payto_hash (payto_uri, - &wd.h_wire_details); - return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS, - &wd, - &master_sig->eddsa_signature, - &master_pub->eddsa_pub); -} - - -void -TALER_exchange_wire_signature_make ( - const char *payto_uri, - const struct TALER_MasterPrivateKeyP *master_priv, - struct TALER_MasterSignatureP *master_sig) -{ - struct TALER_MasterWireDetailsPS wd = { - .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS), - .purpose.size = htonl (sizeof (wd)) - }; - - TALER_payto_hash (payto_uri, - &wd.h_wire_details); - GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, - &wd, - &master_sig->eddsa_signature); -} - - void TALER_merchant_wire_signature_hash (const char *payto_uri, - const struct TALER_WireSalt *salt, - struct TALER_MerchantWireHash *hc) + const struct TALER_WireSaltP *salt, + struct TALER_MerchantWireHashP *hc) { GNUNET_assert (GNUNET_YES == GNUNET_CRYPTO_kdf (hc, @@ -80,47 +41,4 @@ TALER_merchant_wire_signature_hash (const char *payto_uri, } -enum GNUNET_GenericReturnValue -TALER_merchant_wire_signature_check ( - const char *payto_uri, - const struct TALER_WireSalt *salt, - const struct TALER_MerchantPublicKeyP *merch_pub, - const struct TALER_MerchantSignatureP *merch_sig) -{ - struct TALER_MerchantWireDetailsPS wd = { - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS), - .purpose.size = htonl (sizeof (wd)) - }; - - TALER_merchant_wire_signature_hash (payto_uri, - salt, - &wd.h_wire_details); - return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS, - &wd, - &merch_sig->eddsa_sig, - &merch_pub->eddsa_pub); -} - - -void -TALER_merchant_wire_signature_make ( - const char *payto_uri, - const struct TALER_WireSalt *salt, - const struct TALER_MerchantPrivateKeyP *merch_priv, - struct TALER_MerchantSignatureP *merch_sig) -{ - struct TALER_MerchantWireDetailsPS wd = { - .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS), - .purpose.size = htonl (sizeof (wd)) - }; - - TALER_merchant_wire_signature_hash (payto_uri, - salt, - &wd.h_wire_details); - GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv, - &wd, - &merch_sig->eddsa_sig); -} - - /* end of crypto_wire.c */ diff --git a/src/util/currencies.conf b/src/util/currencies.conf new file mode 100644 index 000000000..0fa831bf3 --- /dev/null +++ b/src/util/currencies.conf @@ -0,0 +1,89 @@ +[currency-euro] +ENABLED = YES +name = "Euro" +code = "EUR" +fractional_input_digits = 2 +fractional_normal_digits = 2 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"€"} + +[currency-swiss-francs] +ENABLED = YES +name = "Swiss Francs" +code = "CHF" +fractional_input_digits = 2 +fractional_normal_digits = 2 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"Fr.","-2":"Rp."} + +[currency-forint] +ENABLED = NO +name = "Hungarian Forint" +code = "HUF" +fractional_input_digits = 0 +fractional_normal_digits = 0 +fractional_trailing_zero_digits = 0 +alt_unit_names = {"0":"Ft"} + +[currency-us-dollar] +ENABLED = NO +name = "US Dollar" +code = "USD" +fractional_input_digits = 2 +fractional_normal_digits = 2 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"$"} + +[currency-kudos] +ENABLED = YES +name = "Kudos (Taler Demonstrator)" +code = "KUDOS" +fractional_input_digits = 2 +fractional_normal_digits = 2 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"ク"} + +[currency-testkudos] +ENABLED = YES +name = "Test-kudos (Taler Demonstrator)" +code = "TESTKUDOS" +fractional_input_digits = 2 +fractional_normal_digits = 2 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"テ","3":"kテ","-3":"mテ"} + +[currency-japanese-yen] +ENABLED = NO +name = "Japanese Yen" +code = "JPY" +fractional_input_digits = 2 +fractional_normal_digits = 0 +fractional_trailing_zero_digits = 2 +alt_unit_names = {"0":"¥"} + +[currency-bitcoin-mainnet] +ENABLED = NO +name = "Bitcoin (Mainnet)" +code = "BITCOINBTC" +fractional_input_digits = 8 +fractional_normal_digits = 3 +fractional_trailing_zero_digits = 0 +alt_unit_names = {"0":"BTC","-3":"mBTC"} + +[currency-ethereum] +ENABLED = NO +name = "WAI-ETHER (Ethereum)" +code = "EthereumWAI" +fractional_input_digits = 0 +fractional_normal_digits = 0 +fractional_trailing_zero_digits = 0 +alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"} + +[currency-netzbon] +ENABLED=YES +name=NetzBon +code=NETZBON +fractional_input_digits=2 +fractional_normal_digits=2 +fractional_trailing_zero_digits=2 +alt_unit_names = {"0":"NETZBON"} diff --git a/src/util/denom.c b/src/util/denom.c index b6b3764da..cb232c4a3 100644 --- a/src/util/denom.c +++ b/src/util/denom.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021, 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 @@ -25,87 +25,43 @@ enum GNUNET_GenericReturnValue TALER_denom_priv_create (struct TALER_DenominationPrivateKey *denom_priv, struct TALER_DenominationPublicKey *denom_pub, - enum TALER_DenominationCipher cipher, + enum GNUNET_CRYPTO_BlindSignatureAlgorithm cipher, ...) { - memset (denom_priv, - 0, - sizeof (*denom_priv)); + enum GNUNET_GenericReturnValue ret; + va_list ap; + memset (denom_pub, 0, sizeof (*denom_pub)); - switch (cipher) - { - case TALER_DENOMINATION_INVALID: - GNUNET_break (0); - return GNUNET_SYSERR; - case TALER_DENOMINATION_RSA: - { - va_list ap; - unsigned int bits; - - va_start (ap, cipher); - bits = va_arg (ap, unsigned int); - va_end (ap); - if (bits < 512) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - denom_priv->details.rsa_private_key - = GNUNET_CRYPTO_rsa_private_key_create (bits); - } - if (NULL == denom_priv->details.rsa_private_key) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - denom_pub->details.rsa_public_key - = GNUNET_CRYPTO_rsa_private_key_get_public ( - denom_priv->details.rsa_private_key); - denom_priv->cipher = cipher; - denom_pub->cipher = cipher; - return GNUNET_OK; - // TODO: add case for Clause-Schnorr - default: - GNUNET_break (0); - } - return GNUNET_SYSERR; + memset (denom_priv, + 0, + sizeof (*denom_priv)); + va_start (ap, + cipher); + ret = GNUNET_CRYPTO_blind_sign_keys_create_va ( + &denom_priv->bsign_priv_key, + &denom_pub->bsign_pub_key, + cipher, + ap); + va_end (ap); + return ret; } enum GNUNET_GenericReturnValue TALER_denom_sign_blinded (struct TALER_BlindedDenominationSignature *denom_sig, const struct TALER_DenominationPrivateKey *denom_priv, - void *blinded_msg, - size_t blinded_msg_size) + bool for_melt, + const struct TALER_BlindedPlanchet *blinded_planchet) { - memset (denom_sig, - 0, - sizeof (*denom_sig)); - switch (denom_priv->cipher) - { - case TALER_DENOMINATION_INVALID: - GNUNET_break (0); + denom_sig->blinded_sig + = GNUNET_CRYPTO_blind_sign (denom_priv->bsign_priv_key, + for_melt ? "rm" : "rw", + blinded_planchet->blinded_message); + if (NULL == denom_sig->blinded_sig) return GNUNET_SYSERR; - case TALER_DENOMINATION_RSA: - denom_sig->details.blinded_rsa_signature - = GNUNET_CRYPTO_rsa_sign_blinded ( - denom_priv->details.rsa_private_key, - blinded_msg, - blinded_msg_size); - if (NULL == denom_sig->details.blinded_rsa_signature) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - denom_sig->cipher = TALER_DENOMINATION_RSA; - return GNUNET_OK; - // TODO: add case for Clause-Schnorr - default: - GNUNET_break (0); - } - return GNUNET_SYSERR; + return GNUNET_OK; } @@ -113,88 +69,52 @@ enum GNUNET_GenericReturnValue TALER_denom_sig_unblind ( struct TALER_DenominationSignature *denom_sig, const struct TALER_BlindedDenominationSignature *bdenom_sig, - const union TALER_DenominationBlindingKeyP *bks, + const union GNUNET_CRYPTO_BlindingSecretP *bks, + const struct TALER_CoinPubHashP *c_hash, + const struct TALER_ExchangeWithdrawValues *alg_values, const struct TALER_DenominationPublicKey *denom_pub) { - if (bdenom_sig->cipher != denom_pub->cipher) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (denom_pub->cipher) + denom_sig->unblinded_sig + = GNUNET_CRYPTO_blind_sig_unblind (bdenom_sig->blinded_sig, + bks, + c_hash, + sizeof (*c_hash), + alg_values->blinding_inputs, + denom_pub->bsign_pub_key); + if (NULL == denom_sig->unblinded_sig) { - case TALER_DENOMINATION_INVALID: - GNUNET_break (0); + GNUNET_break_op (0); return GNUNET_SYSERR; - case TALER_DENOMINATION_RSA: - denom_sig->details.rsa_signature - = GNUNET_CRYPTO_rsa_unblind ( - bdenom_sig->details.blinded_rsa_signature, - &bks->rsa_bks, - denom_pub->details.rsa_public_key); - if (NULL == denom_sig->details.rsa_signature) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - denom_sig->cipher = TALER_DENOMINATION_RSA; - return GNUNET_OK; - // TODO: add case for Clause-Schnorr - default: - GNUNET_break (0); } - return GNUNET_SYSERR; -} - - -void -TALER_blinding_secret_create (union TALER_DenominationBlindingKeyP *bs) -{ - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - bs, - sizeof (*bs)); -} - - -/** - * Hash @a rsa. - * - * @param rsa key to hash - * @param[out] h_rsa where to write the result - */ -void -TALER_rsa_pub_hash (const struct GNUNET_CRYPTO_RsaPublicKey *rsa, - struct TALER_RsaPubHashP *h_rsa) -{ - GNUNET_CRYPTO_rsa_public_key_hash (rsa, - &h_rsa->hash); - + return GNUNET_OK; } void TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub, - struct TALER_DenominationHash *denom_hash) + struct TALER_DenominationHashP *denom_hash) { + struct GNUNET_CRYPTO_BlindSignPublicKey *bsp + = denom_pub->bsign_pub_key; uint32_t opt[2] = { - htonl (denom_pub->age_mask.mask), - htonl ((uint32_t) denom_pub->cipher) + htonl (denom_pub->age_mask.bits), + htonl ((uint32_t) bsp->cipher) }; - struct GNUNET_HashContext *hc; + hc = GNUNET_CRYPTO_hash_context_start (); GNUNET_CRYPTO_hash_context_read (hc, opt, sizeof (opt)); - switch (denom_pub->cipher) + switch (bsp->cipher) { - case TALER_DENOMINATION_RSA: + case GNUNET_CRYPTO_BSA_RSA: { void *buf; size_t blen; blen = GNUNET_CRYPTO_rsa_public_key_encode ( - denom_pub->details.rsa_public_key, + bsp->details.rsa_public_key, &buf); GNUNET_CRYPTO_hash_context_read (hc, buf, @@ -202,7 +122,11 @@ TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub, GNUNET_free (buf); } break; - // TODO: add case for Clause-Schnorr + case GNUNET_CRYPTO_BSA_CS: + GNUNET_CRYPTO_hash_context_read (hc, + &bsp->details.cs_public_key, + sizeof(bsp->details.cs_public_key)); + break; default: GNUNET_assert (0); } @@ -211,112 +135,65 @@ TALER_denom_pub_hash (const struct TALER_DenominationPublicKey *denom_pub, } -void -TALER_denom_priv_to_pub (const struct TALER_DenominationPrivateKey *denom_priv, - const struct TALER_AgeMask age_mask, - struct TALER_DenominationPublicKey *denom_pub) +const struct TALER_ExchangeWithdrawValues * +TALER_denom_ewv_rsa_singleton () { - switch (denom_priv->cipher) - { - case TALER_DENOMINATION_RSA: - denom_pub->cipher = TALER_DENOMINATION_RSA; - denom_pub->age_mask = age_mask; - denom_pub->details.rsa_public_key - = GNUNET_CRYPTO_rsa_private_key_get_public ( - denom_priv->details.rsa_private_key); - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); - } + static struct GNUNET_CRYPTO_BlindingInputValues bi = { + .cipher = GNUNET_CRYPTO_BSA_RSA + }; + static struct TALER_ExchangeWithdrawValues alg_values = { + .blinding_inputs = &bi + }; + return &alg_values; } enum GNUNET_GenericReturnValue -TALER_denom_blind (const struct TALER_DenominationPublicKey *dk, - const union TALER_DenominationBlindingKeyP *coin_bks, - const struct TALER_AgeHash *age_commitment_hash, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_CoinPubHash *c_hash, - void **coin_ev, - size_t *coin_ev_size) +TALER_denom_blind ( + const struct TALER_DenominationPublicKey *dk, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const union GNUNET_CRYPTO_BlindSessionNonce *nonce, + const struct TALER_AgeCommitmentHash *ach, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_ExchangeWithdrawValues *alg_values, + struct TALER_CoinPubHashP *c_hash, + struct TALER_BlindedPlanchet *blinded_planchet) { - switch (dk->cipher) - { - case TALER_DENOMINATION_RSA: - TALER_coin_pub_hash (coin_pub, - age_commitment_hash, - c_hash); - if (GNUNET_YES != - GNUNET_CRYPTO_rsa_blind (&c_hash->hash, - &coin_bks->rsa_bks, - dk->details.rsa_public_key, - coin_ev, - coin_ev_size)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; - // TODO: add case for Clause-Schnorr - default: - GNUNET_break (0); + TALER_coin_pub_hash (coin_pub, + ach, + c_hash); + blinded_planchet->blinded_message + = GNUNET_CRYPTO_message_blind_to_sign (dk->bsign_pub_key, + coin_bks, + nonce, + c_hash, + sizeof (*c_hash), + alg_values->blinding_inputs); + if (NULL == blinded_planchet->blinded_message) return GNUNET_SYSERR; - } + return GNUNET_OK; } enum GNUNET_GenericReturnValue TALER_denom_pub_verify (const struct TALER_DenominationPublicKey *denom_pub, const struct TALER_DenominationSignature *denom_sig, - const struct TALER_CoinPubHash *c_hash) + const struct TALER_CoinPubHashP *c_hash) { - if (denom_pub->cipher != denom_sig->cipher) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - switch (denom_pub->cipher) - { - case TALER_DENOMINATION_INVALID: - GNUNET_break (0); - return GNUNET_NO; - case TALER_DENOMINATION_RSA: - if (GNUNET_OK != - GNUNET_CRYPTO_rsa_verify (&c_hash->hash, - denom_sig->details.rsa_signature, - denom_pub->details.rsa_public_key)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Coin signature is invalid\n"); - return GNUNET_NO; - } - return GNUNET_YES; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); - } + return GNUNET_CRYPTO_blind_sig_verify (denom_pub->bsign_pub_key, + denom_sig->unblinded_sig, + c_hash, + sizeof (*c_hash)); } void TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub) { - switch (denom_pub->cipher) + if (NULL != denom_pub->bsign_pub_key) { - case TALER_DENOMINATION_INVALID: - return; - case TALER_DENOMINATION_RSA: - if (NULL != denom_pub->details.rsa_public_key) - { - GNUNET_CRYPTO_rsa_public_key_free (denom_pub->details.rsa_public_key); - denom_pub->details.rsa_public_key = NULL; - } - denom_pub->cipher = TALER_DENOMINATION_INVALID; - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + GNUNET_CRYPTO_blind_sign_pub_decref (denom_pub->bsign_pub_key); + denom_pub->bsign_pub_key = NULL; } } @@ -324,21 +201,10 @@ TALER_denom_pub_free (struct TALER_DenominationPublicKey *denom_pub) void TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv) { - switch (denom_priv->cipher) + if (NULL != denom_priv->bsign_priv_key) { - case TALER_DENOMINATION_INVALID: - return; - case TALER_DENOMINATION_RSA: - if (NULL != denom_priv->details.rsa_private_key) - { - GNUNET_CRYPTO_rsa_private_key_free (denom_priv->details.rsa_private_key); - denom_priv->details.rsa_private_key = NULL; - } - denom_priv->cipher = TALER_DENOMINATION_INVALID; - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + GNUNET_CRYPTO_blind_sign_priv_decref (denom_priv->bsign_priv_key); + denom_priv->bsign_priv_key = NULL; } } @@ -346,21 +212,10 @@ TALER_denom_priv_free (struct TALER_DenominationPrivateKey *denom_priv) void TALER_denom_sig_free (struct TALER_DenominationSignature *denom_sig) { - switch (denom_sig->cipher) + if (NULL != denom_sig->unblinded_sig) { - case TALER_DENOMINATION_INVALID: - return; - case TALER_DENOMINATION_RSA: - if (NULL != denom_sig->details.rsa_signature) - { - GNUNET_CRYPTO_rsa_signature_free (denom_sig->details.rsa_signature); - denom_sig->details.rsa_signature = NULL; - } - denom_sig->cipher = TALER_DENOMINATION_INVALID; - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + GNUNET_CRYPTO_unblinded_sig_decref (denom_sig->unblinded_sig); + denom_sig->unblinded_sig = NULL; } } @@ -369,92 +224,73 @@ void TALER_blinded_denom_sig_free ( struct TALER_BlindedDenominationSignature *denom_sig) { - switch (denom_sig->cipher) + if (NULL != denom_sig->blinded_sig) { - case TALER_DENOMINATION_INVALID: - return; - case TALER_DENOMINATION_RSA: - if (NULL != denom_sig->details.blinded_rsa_signature) - { - GNUNET_CRYPTO_rsa_signature_free ( - denom_sig->details.blinded_rsa_signature); - denom_sig->details.blinded_rsa_signature = NULL; - } - denom_sig->cipher = TALER_DENOMINATION_INVALID; - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + GNUNET_CRYPTO_blinded_sig_decref (denom_sig->blinded_sig); + denom_sig->blinded_sig = NULL; } } -/** - * Make a (deep) copy of the given @a denom_src to - * @a denom_dst. - * - * @param[out] denom_dst target to copy to - * @param denom_str public key to copy - */ void -TALER_denom_pub_deep_copy (struct TALER_DenominationPublicKey *denom_dst, - const struct TALER_DenominationPublicKey *denom_src) +TALER_denom_ewv_free (struct TALER_ExchangeWithdrawValues *ewv) { - *denom_dst = *denom_src; /* shallow copy */ - switch (denom_src->cipher) + if (ewv == TALER_denom_ewv_rsa_singleton ()) + return; + if (ewv->blinding_inputs == + TALER_denom_ewv_rsa_singleton ()->blinding_inputs) { - case TALER_DENOMINATION_RSA: - denom_dst->details.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_dup ( - denom_src->details.rsa_public_key); + ewv->blinding_inputs = NULL; return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + } + if (NULL != ewv->blinding_inputs) + { + GNUNET_CRYPTO_blinding_input_values_decref (ewv->blinding_inputs); + ewv->blinding_inputs = NULL; } } void -TALER_denom_sig_deep_copy (struct TALER_DenominationSignature *denom_dst, - const struct TALER_DenominationSignature *denom_src) +TALER_denom_ewv_copy (struct TALER_ExchangeWithdrawValues *bi_dst, + const struct TALER_ExchangeWithdrawValues *bi_src) { - *denom_dst = *denom_src; /* shallow copy */ - switch (denom_src->cipher) + if (bi_src == TALER_denom_ewv_rsa_singleton ()) { - case TALER_DENOMINATION_INVALID: + *bi_dst = *bi_src; return; - case TALER_DENOMINATION_RSA: - denom_dst->details.rsa_signature - = GNUNET_CRYPTO_rsa_signature_dup ( - denom_src->details.rsa_signature); - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); } + bi_dst->blinding_inputs + = GNUNET_CRYPTO_blinding_input_values_incref (bi_src->blinding_inputs); +} + + +void +TALER_denom_pub_copy (struct TALER_DenominationPublicKey *denom_dst, + const struct TALER_DenominationPublicKey *denom_src) +{ + denom_dst->age_mask = denom_src->age_mask; + denom_dst->bsign_pub_key + = GNUNET_CRYPTO_bsign_pub_incref (denom_src->bsign_pub_key); } void -TALER_blinded_denom_sig_deep_copy ( +TALER_denom_sig_copy (struct TALER_DenominationSignature *denom_dst, + const struct TALER_DenominationSignature *denom_src) +{ + denom_dst->unblinded_sig + = GNUNET_CRYPTO_ub_sig_incref (denom_src->unblinded_sig); +} + + +void +TALER_blinded_denom_sig_copy ( struct TALER_BlindedDenominationSignature *denom_dst, const struct TALER_BlindedDenominationSignature *denom_src) { - *denom_dst = *denom_src; /* shallow copy */ - switch (denom_src->cipher) - { - case TALER_DENOMINATION_INVALID: - return; - case TALER_DENOMINATION_RSA: - denom_dst->details.blinded_rsa_signature - = GNUNET_CRYPTO_rsa_signature_dup ( - denom_src->details.blinded_rsa_signature); - return; - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); - } + denom_dst->blinded_sig + = GNUNET_CRYPTO_blind_sig_incref (denom_src->blinded_sig); } @@ -462,22 +298,14 @@ int TALER_denom_pub_cmp (const struct TALER_DenominationPublicKey *denom1, const struct TALER_DenominationPublicKey *denom2) { - if (denom1->cipher != denom2->cipher) - return (denom1->cipher > denom2->cipher) ? 1 : -1; - if (denom1->age_mask.mask != denom2->age_mask.mask) - return (denom1->age_mask.mask > denom2->age_mask.mask) ? 1 : -1; - switch (denom1->cipher) - { - case TALER_DENOMINATION_INVALID: - return 0; - case TALER_DENOMINATION_RSA: - return GNUNET_CRYPTO_rsa_public_key_cmp (denom1->details.rsa_public_key, - denom2->details.rsa_public_key); - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); - } - return -2; + if (denom1->bsign_pub_key->cipher != + denom2->bsign_pub_key->cipher) + return (denom1->bsign_pub_key->cipher > + denom2->bsign_pub_key->cipher) ? 1 : -1; + if (denom1->age_mask.bits != denom2->age_mask.bits) + return (denom1->age_mask.bits > denom2->age_mask.bits) ? 1 : -1; + return GNUNET_CRYPTO_bsign_pub_cmp (denom1->bsign_pub_key, + denom2->bsign_pub_key); } @@ -485,20 +313,18 @@ int TALER_denom_sig_cmp (const struct TALER_DenominationSignature *sig1, const struct TALER_DenominationSignature *sig2) { - if (sig1->cipher != sig2->cipher) - return (sig1->cipher > sig2->cipher) ? 1 : -1; - switch (sig1->cipher) - { - case TALER_DENOMINATION_INVALID: - return 0; - case TALER_DENOMINATION_RSA: - return GNUNET_CRYPTO_rsa_signature_cmp (sig1->details.rsa_signature, - sig2->details.rsa_signature); - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); - } - return -2; + return GNUNET_CRYPTO_ub_sig_cmp (sig1->unblinded_sig, + sig1->unblinded_sig); +} + + +int +TALER_blinded_planchet_cmp ( + const struct TALER_BlindedPlanchet *bp1, + const struct TALER_BlindedPlanchet *bp2) +{ + return GNUNET_CRYPTO_blinded_message_cmp (bp1->blinded_message, + bp2->blinded_message); } @@ -507,20 +333,140 @@ TALER_blinded_denom_sig_cmp ( const struct TALER_BlindedDenominationSignature *sig1, const struct TALER_BlindedDenominationSignature *sig2) { - if (sig1->cipher != sig2->cipher) - return (sig1->cipher > sig2->cipher) ? 1 : -1; - switch (sig1->cipher) + return GNUNET_CRYPTO_blind_sig_cmp (sig1->blinded_sig, + sig1->blinded_sig); +} + + +void +TALER_blinded_planchet_hash_ (const struct TALER_BlindedPlanchet *bp, + struct GNUNET_HashContext *hash_context) +{ + const struct GNUNET_CRYPTO_BlindedMessage *bm = bp->blinded_message; + uint32_t cipher = htonl (bm->cipher); + + GNUNET_CRYPTO_hash_context_read (hash_context, + &cipher, + sizeof (cipher)); + switch (bm->cipher) { - case TALER_DENOMINATION_INVALID: - return 0; - case TALER_DENOMINATION_RSA: - return GNUNET_CRYPTO_rsa_signature_cmp (sig1->details.blinded_rsa_signature, - sig2->details.blinded_rsa_signature); - // TODO: add case for Clause-Schnorr - default: - GNUNET_assert (0); + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + return; + case GNUNET_CRYPTO_BSA_RSA: + GNUNET_CRYPTO_hash_context_read ( + hash_context, + bm->details.rsa_blinded_message.blinded_msg, + bm->details.rsa_blinded_message.blinded_msg_size); + return; + case GNUNET_CRYPTO_BSA_CS: + GNUNET_CRYPTO_hash_context_read ( + hash_context, + &bm->details.cs_blinded_message, + sizeof (bm->details.cs_blinded_message)); + return; + } + GNUNET_assert (0); +} + + +void +TALER_planchet_blinding_secret_create ( + const struct TALER_PlanchetMasterSecretP *ps, + const struct TALER_ExchangeWithdrawValues *alg_values, + union GNUNET_CRYPTO_BlindingSecretP *bks) +{ + const struct GNUNET_CRYPTO_BlindingInputValues *bi = + alg_values->blinding_inputs; + + switch (bi->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + return; + case GNUNET_CRYPTO_BSA_RSA: + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (&bks->rsa_bks, + sizeof (bks->rsa_bks), + "bks", + strlen ("bks"), + ps, + sizeof(*ps), + NULL, + 0)); + return; + case GNUNET_CRYPTO_BSA_CS: + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (&bks->nonce, + sizeof (bks->nonce), + "bseed", + strlen ("bseed"), + ps, + sizeof(*ps), + &bi->details.cs_values, + sizeof(bi->details.cs_values), + NULL, + 0)); + return; + } + GNUNET_assert (0); +} + + +void +TALER_planchet_setup_coin_priv ( + const struct TALER_PlanchetMasterSecretP *ps, + const struct TALER_ExchangeWithdrawValues *alg_values, + struct TALER_CoinSpendPrivateKeyP *coin_priv) +{ + const struct GNUNET_CRYPTO_BlindingInputValues *bi + = alg_values->blinding_inputs; + + switch (bi->cipher) + { + case GNUNET_CRYPTO_BSA_INVALID: + GNUNET_break (0); + memset (coin_priv, + 0, + sizeof (*coin_priv)); + return; + case GNUNET_CRYPTO_BSA_RSA: + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (coin_priv, + sizeof (*coin_priv), + "coin", + strlen ("coin"), + ps, + sizeof(*ps), + NULL, + 0)); + return; + case GNUNET_CRYPTO_BSA_CS: + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf (coin_priv, + sizeof (*coin_priv), + "coin", + strlen ("coin"), + ps, + sizeof(*ps), + &bi->details.cs_values, + sizeof(bi->details.cs_values), + NULL, + 0)); + return; + } + GNUNET_assert (0); +} + + +void +TALER_blinded_planchet_free (struct TALER_BlindedPlanchet *blinded_planchet) +{ + if (NULL != blinded_planchet->blinded_message) + { + GNUNET_CRYPTO_blinded_message_decref (blinded_planchet->blinded_message); + blinded_planchet->blinded_message = NULL; } - return -2; } diff --git a/src/util/do_bench_age_restriction b/src/util/do_bench_age_restriction new file mode 100755 index 000000000..a65713439 --- /dev/null +++ b/src/util/do_bench_age_restriction @@ -0,0 +1,8 @@ +#!/bin/sh + +gcc bench_age_restriction.c \ + -lgnunetutil -lgnunetjson -lsodium -ljansson \ + -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil -lm \ + -I../include \ + -o bench_age_restriction && ./bench_age_restriction + diff --git a/src/util/exchange_signatures.c b/src/util/exchange_signatures.c index 2e71a33c1..aaefb5cec 100644 --- a/src/util/exchange_signatures.c +++ b/src/util/exchange_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021-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 @@ -23,16 +23,136 @@ #include "taler_signatures.h" +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on a confirmation + * from the exchange that a deposit request succeeded. + */ +struct TALER_DepositConfirmationPS +{ + /** + * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT. Signed + * by a `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the contract for which this deposit is made. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; + + /** + * Hash over the optional policy extension of the deposit, 0 if there + * was no policy. + */ + struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED; + + /** + * Time when this confirmation was generated / when the exchange received + * the deposit request. + */ + struct GNUNET_TIME_TimestampNBO exchange_timestamp; + + /** + * By when does the exchange expect to pay the merchant + * (as per the merchant's request). + */ + struct GNUNET_TIME_TimestampNBO wire_deadline; + + /** + * How much time does the @e merchant have to issue a refund + * request? Zero if refunds are not allowed. After this time, the + * coin cannot be refunded. Note that the wire transfer will not be + * performed by the exchange until the refund deadline. This value + * is taken from the original deposit request. + */ + struct GNUNET_TIME_TimestampNBO refund_deadline; + + /** + * Amount to be deposited, excluding fee. Calculated from the + * amount with fee and the fee from the deposit request. + */ + struct TALER_AmountNBO total_without_fee; + + /** + * Hash over all of the coin signatures. + */ + struct GNUNET_HashCode h_coin_sigs; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction or to inquire about the wire transfer identifier. + */ + struct TALER_MerchantPublicKeyP merchant_pub; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_deposit_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExtensionPolicyHashP *h_policy, + struct GNUNET_TIME_Timestamp exchange_timestamp, + struct GNUNET_TIME_Timestamp wire_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_DepositConfirmationPS dcs = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT), + .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)), + .h_contract_terms = *h_contract_terms, + .h_wire = *h_wire, + .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp), + .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline), + .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), + .merchant_pub = *merchant_pub, + .h_policy = {{{0}}} + }; + struct GNUNET_HashContext *hc; + + hc = GNUNET_CRYPTO_hash_context_start (); + for (unsigned int i = 0; i<num_coins; i++) + GNUNET_CRYPTO_hash_context_read (hc, + coin_sigs[i], + sizeof (*coin_sigs[i])); + GNUNET_CRYPTO_hash_context_finish (hc, + &dcs.h_coin_sigs); + if (NULL != h_policy) + dcs.h_policy = *h_policy; + TALER_amount_hton (&dcs.total_without_fee, + total_without_fee); + return scb (&dcs.purpose, + pub, + sig); +} + + enum GNUNET_GenericReturnValue -TALER_exchange_deposit_confirm_verify ( - const struct TALER_PrivateContractHash *h_contract_terms, - const struct TALER_MerchantWireHash *h_wire, - const struct TALER_ExtensionContractHash *h_extensions, - struct GNUNET_TIME_Absolute exchange_timestamp, - struct GNUNET_TIME_Absolute wire_deadline, - struct GNUNET_TIME_Absolute refund_deadline, - const struct TALER_Amount *amount_without_fee, - const struct TALER_CoinSpendPublicKeyP *coin_pub, +TALER_exchange_online_deposit_confirmation_verify ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_ExtensionPolicyHashP *h_policy, + struct GNUNET_TIME_Timestamp exchange_timestamp, + struct GNUNET_TIME_Timestamp wire_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, + const struct TALER_Amount *total_without_fee, + unsigned int num_coins, + const struct TALER_CoinSpendSignatureP *coin_sigs[static num_coins], const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_ExchangeSignatureP *exchange_sig) @@ -42,17 +162,24 @@ TALER_exchange_deposit_confirm_verify ( .purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)), .h_contract_terms = *h_contract_terms, .h_wire = *h_wire, - .exchange_timestamp = GNUNET_TIME_absolute_hton (exchange_timestamp), - .wire_deadline = GNUNET_TIME_absolute_hton (wire_deadline), - .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline), - .coin_pub = *coin_pub, + .exchange_timestamp = GNUNET_TIME_timestamp_hton (exchange_timestamp), + .wire_deadline = GNUNET_TIME_timestamp_hton (wire_deadline), + .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), .merchant_pub = *merchant_pub }; + struct GNUNET_HashContext *hc; - if (NULL != h_extensions) - dcs.h_extensions = *h_extensions; - TALER_amount_hton (&dcs.amount_without_fee, - amount_without_fee); + hc = GNUNET_CRYPTO_hash_context_start (); + for (unsigned int i = 0; i<num_coins; i++) + GNUNET_CRYPTO_hash_context_read (hc, + coin_sigs[i], + sizeof (*coin_sigs[i])); + GNUNET_CRYPTO_hash_context_finish (hc, + &dcs.h_coin_sigs); + if (NULL != h_policy) + dcs.h_policy = *h_policy; + TALER_amount_hton (&dcs.total_without_fee, + total_without_fee); if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_DEPOSIT, &dcs, @@ -66,4 +193,1702 @@ TALER_exchange_deposit_confirm_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on a request to refund + * a coin into the account of the customer. + */ +struct TALER_RefundConfirmationPS +{ + /** + * Purpose must be #TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the proposal data to identify the contract + * which is being refunded. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction or to inquire about the wire transfer identifier. + */ + struct TALER_MerchantPublicKeyP merchant; + + /** + * Merchant-generated transaction ID for the refund. + */ + uint64_t rtransaction_id GNUNET_PACKED; + + /** + * Amount to be refunded, including refund fee charged by the + * exchange to the customer. + */ + struct TALER_AmountNBO refund_amount; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_refund_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant, + uint64_t rtransaction_id, + const struct TALER_Amount *refund_amount, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RefundConfirmationPS rc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), + .purpose.size = htonl (sizeof (rc)), + .h_contract_terms = *h_contract_terms, + .coin_pub = *coin_pub, + .merchant = *merchant, + .rtransaction_id = GNUNET_htonll (rtransaction_id) + }; + + TALER_amount_hton (&rc.refund_amount, + refund_amount); + return scb (&rc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_refund_confirmation_verify ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPublicKeyP *merchant, + uint64_t rtransaction_id, + const struct TALER_Amount *refund_amount, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RefundConfirmationPS rc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), + .purpose.size = htonl (sizeof (rc)), + .h_contract_terms = *h_contract_terms, + .coin_pub = *coin_pub, + .merchant = *merchant, + .rtransaction_id = GNUNET_htonll (rtransaction_id) + }; + + TALER_amount_hton (&rc.refund_amount, + refund_amount); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND, + &rc, + &sig->eddsa_signature, + &pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format of the block signed by the Exchange in response to a successful + * "/refresh/melt" request. Hereby the exchange affirms that all of the + * coins were successfully melted. This also commits the exchange to a + * particular index to not be revealed during the refresh. + */ +struct TALER_RefreshMeltConfirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT. Signed + * by a `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Commitment made in the /refresh/melt. + */ + struct TALER_RefreshCommitmentP rc GNUNET_PACKED; + + /** + * Index that the client will not have to reveal, in NBO. + * Must be smaller than #TALER_CNC_KAPPA. + */ + uint32_t noreveal_index GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_melt_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_RefreshCommitmentP *rc, + uint32_t noreveal_index, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RefreshMeltConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT), + .purpose.size = htonl (sizeof (confirm)), + .rc = *rc, + .noreveal_index = htonl (noreveal_index) + }; + + return scb (&confirm.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_melt_confirmation_verify ( + const struct TALER_RefreshCommitmentP *rc, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct TALER_RefreshMeltConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT), + .purpose.size = htonl (sizeof (confirm)), + .rc = *rc, + .noreveal_index = htonl (noreveal_index) + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_MELT, + &confirm, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format of the block signed by the Exchange in response to a + * successful "/reserves/$RESERVE_PUB/age-withdraw" request. Hereby the + * exchange affirms that the commitment along with the maximum age group and + * the amount were accepted. This also commits the exchange to a particular + * index to not be revealed during the reveal. + */ +struct TALER_AgeWithdrawConfirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW. Signed by a + * `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Commitment made in the /reserves/$RESERVE_PUB/age-withdraw. + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment GNUNET_PACKED; + + /** + * Index that the client will not have to reveal, in NBO. + * Must be smaller than #TALER_CNC_KAPPA. + */ + uint32_t noreveal_index GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + +enum TALER_ErrorCode +TALER_exchange_online_age_withdraw_confirmation_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + + struct TALER_AgeWithdrawConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW), + .purpose.size = htonl (sizeof (confirm)), + .h_commitment = *h_commitment, + .noreveal_index = htonl (noreveal_index) + }; + + return scb (&confirm.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_age_withdraw_confirmation_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + uint32_t noreveal_index, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct TALER_AgeWithdrawConfirmationPS confirm = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW), + .purpose.size = htonl (sizeof (confirm)), + .h_commitment = *h_commitment, + .noreveal_index = htonl (noreveal_index) + }; + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_AGE_WITHDRAW, + &confirm, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/* TODO:oec: add signature for age-withdraw, age-reveal */ + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange over the full set of keys, used + * to detect cheating exchanges that give out different sets to + * different users. + */ +struct TALER_ExchangeKeySetPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_KEY_SET. Signed + * by a `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the key set issue. + */ + struct GNUNET_TIME_TimestampNBO list_issue_date; + + /** + * Hash over the various denomination signing keys returned. + */ + struct GNUNET_HashCode hc GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_key_set_sign ( + TALER_ExchangeSignCallback2 scb, + void *cls, + struct GNUNET_TIME_Timestamp timestamp, + const struct GNUNET_HashCode *hc, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeKeySetPS ks = { + .purpose.size = htonl (sizeof (ks)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET), + .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp), + .hc = *hc + }; + + return scb (cls, + &ks.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_key_set_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct GNUNET_HashCode *hc, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeKeySetPS ks = { + .purpose.size = htonl (sizeof (ks)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_KEY_SET), + .list_issue_date = GNUNET_TIME_timestamp_hton (timestamp), + .hc = *hc + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_KEY_SET, + &ks, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature by which an exchange affirms that an account + * successfully passed the KYC checks. + */ +struct TALER_ExchangeAccountSetupSuccessPS +{ + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS. Signed by a + * `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the payto for which the signature was made. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Hash over details on *which* KYC obligations were discharged! + */ + struct GNUNET_HashCode h_kyc; + + /** + * When was the signature made. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_account_setup_success_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_PaytoHashP *h_payto, + const json_t *kyc, + struct GNUNET_TIME_Timestamp timestamp, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = { + .purpose.size = htonl (sizeof (kyc_purpose)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS), + .h_payto = *h_payto, + .timestamp = GNUNET_TIME_timestamp_hton (timestamp) + }; + + TALER_json_hash (kyc, + &kyc_purpose.h_kyc); + return scb (&kyc_purpose.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_account_setup_success_verify ( + const struct TALER_PaytoHashP *h_payto, + const json_t *kyc, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeAccountSetupSuccessPS kyc_purpose = { + .purpose.size = htonl (sizeof (kyc_purpose)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS), + .h_payto = *h_payto, + .timestamp = GNUNET_TIME_timestamp_hton (timestamp) + }; + + TALER_json_hash (kyc, + &kyc_purpose.h_kyc); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_ACCOUNT_SETUP_SUCCESS, + &kyc_purpose, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format internally used for packing the detailed information + * to generate the signature for /track/transfer signatures. + */ +struct TALER_WireDepositDetailP +{ + + /** + * Hash of the contract + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Time when the wire transfer was performed by the exchange. + */ + struct GNUNET_TIME_TimestampNBO execution_time; + + /** + * Coin's public key. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Total value of the coin. + */ + struct TALER_AmountNBO deposit_value; + + /** + * Fees charged by the exchange for the deposit. + */ + struct TALER_AmountNBO deposit_fee; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_online_wire_deposit_append ( + struct GNUNET_HashContext *hash_context, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct GNUNET_TIME_Timestamp execution_time, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_Amount *deposit_value, + const struct TALER_Amount *deposit_fee) +{ + struct TALER_WireDepositDetailP dd = { + .h_contract_terms = *h_contract_terms, + .execution_time = GNUNET_TIME_timestamp_hton (execution_time), + .coin_pub = *coin_pub + }; + TALER_amount_hton (&dd.deposit_value, + deposit_value); + TALER_amount_hton (&dd.deposit_fee, + deposit_fee); + GNUNET_CRYPTO_hash_context_read (hash_context, + &dd, + sizeof (dd)); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature for /wire/deposit + * replies. + */ +struct TALER_WireDepositDataPS +{ + /** + * Purpose header for the signature over the contract with + * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Total amount that was transferred. + */ + struct TALER_AmountNBO total; + + /** + * Wire fee that was charged. + */ + struct TALER_AmountNBO wire_fee; + + /** + * Public key of the merchant (for all aggregated transactions). + */ + struct TALER_MerchantPublicKeyP merchant_pub; + + /** + * Hash of bank account of the merchant. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Hash of the individual deposits that were aggregated, + * each in the format of a `struct TALER_WireDepositDetailP`. + */ + struct GNUNET_HashCode h_details; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_wire_deposit_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_Amount *total, + const struct TALER_Amount *wire_fee, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const char *payto, + const struct GNUNET_HashCode *h_details, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_WireDepositDataPS wdp = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT), + .purpose.size = htonl (sizeof (wdp)), + .merchant_pub = *merchant_pub, + .h_details = *h_details + }; + + TALER_amount_hton (&wdp.total, + total); + TALER_amount_hton (&wdp.wire_fee, + wire_fee); + TALER_payto_hash (payto, + &wdp.h_payto); + return scb (&wdp.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_wire_deposit_verify ( + const struct TALER_Amount *total, + const struct TALER_Amount *wire_fee, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_PaytoHashP *h_payto, + const struct GNUNET_HashCode *h_details, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_WireDepositDataPS wdp = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT), + .purpose.size = htonl (sizeof (wdp)), + .merchant_pub = *merchant_pub, + .h_details = *h_details, + .h_payto = *h_payto + }; + + TALER_amount_hton (&wdp.total, + total); + TALER_amount_hton (&wdp.wire_fee, + wire_fee); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE_DEPOSIT, + &wdp, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Details affirmed by the exchange about a wire transfer the exchange + * claims to have done with respect to a deposit operation. + */ +struct TALER_ConfirmWirePS +{ + /** + * Purpose header for the signature over the contract with + * purpose #TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; + + /** + * Hash over the contract for which this deposit is made. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * Raw value (binary encoding) of the wire transfer subject. + */ + struct TALER_WireTransferIdentifierRawP wtid; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * When did the exchange execute this transfer? Note that the + * timestamp may not be exactly the same on the wire, i.e. + * because the wire has a different timezone or resolution. + */ + struct GNUNET_TIME_TimestampNBO execution_time; + + /** + * The contribution of @e coin_pub to the total transfer volume. + * This is the value of the deposit minus the fee. + */ + struct TALER_AmountNBO coin_contribution; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_confirm_wire_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Timestamp execution_time, + const struct TALER_Amount *coin_contribution, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) + +{ + struct TALER_ConfirmWirePS cw = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE), + .purpose.size = htonl (sizeof (cw)), + .h_wire = *h_wire, + .h_contract_terms = *h_contract_terms, + .wtid = *wtid, + .coin_pub = *coin_pub, + .execution_time = GNUNET_TIME_timestamp_hton (execution_time) + }; + + TALER_amount_hton (&cw.coin_contribution, + coin_contribution); + return scb (&cw.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_confirm_wire_verify ( + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + struct GNUNET_TIME_Timestamp execution_time, + const struct TALER_Amount *coin_contribution, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ConfirmWirePS cw = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE), + .purpose.size = htonl (sizeof (cw)), + .h_wire = *h_wire, + .h_contract_terms = *h_contract_terms, + .wtid = *wtid, + .coin_pub = *coin_pub, + .execution_time = GNUNET_TIME_timestamp_hton (execution_time) + }; + + TALER_amount_hton (&cw.coin_contribution, + coin_contribution); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE, + &cw, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it will + * refund a coin as part of the emergency /recoup + * protocol. The recoup will go back to the bank + * account that created the reserve. + */ +struct TALER_RecoupConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange receive the recoup request? + * Indirectly determines when the wire transfer is (likely) + * to happen. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + + /** + * How much of the coin's value will the exchange transfer? + * (Needed in case the coin was partially spent.) + */ + struct TALER_AmountNBO recoup_amount; + + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Public key of the reserve that will receive the recoup. + */ + struct TALER_ReservePublicKeyP reserve_pub; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_confirm_recoup_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *recoup_amount, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RecoupConfirmationPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .coin_pub = *coin_pub, + .reserve_pub = *reserve_pub + }; + + TALER_amount_hton (&pc.recoup_amount, + recoup_amount); + return scb (&pc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_confirm_recoup_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *recoup_amount, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RecoupConfirmationPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .coin_pub = *coin_pub, + .reserve_pub = *reserve_pub + }; + + TALER_amount_hton (&pc.recoup_amount, + recoup_amount); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP, + &pc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it will refund a refreshed coin + * as part of the emergency /recoup protocol. The recoup will go back to the + * old coin's balance. + */ +struct TALER_RecoupRefreshConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange receive the recoup request? + * Indirectly determines when the wire transfer is (likely) + * to happen. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + + /** + * How much of the coin's value will the exchange transfer? + * (Needed in case the coin was partially spent.) + */ + struct TALER_AmountNBO recoup_amount; + + /** + * Public key of the refreshed coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Public key of the old coin that will receive the recoup. + */ + struct TALER_CoinSpendPublicKeyP old_coin_pub; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_confirm_recoup_refresh_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *recoup_amount, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendPublicKeyP *old_coin_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RecoupRefreshConfirmationPS pc = { + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH), + .purpose.size = htonl (sizeof (pc)), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .coin_pub = *coin_pub, + .old_coin_pub = *old_coin_pub + }; + + TALER_amount_hton (&pc.recoup_amount, + recoup_amount); + return scb (&pc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_confirm_recoup_refresh_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *recoup_amount, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendPublicKeyP *old_coin_pub, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_RecoupRefreshConfirmationPS pc = { + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH), + .purpose.size = htonl (sizeof (pc)), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .coin_pub = *coin_pub, + .old_coin_pub = *old_coin_pub + }; + + TALER_amount_hton (&pc.recoup_amount, + recoup_amount); + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_RECOUP_REFRESH, + &pc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it does not + * currently know a denomination by the given hash. + */ +struct TALER_DenominationUnknownAffirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange sign this message. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + + /** + * Hash of the public denomination key we do not know. + */ + struct TALER_DenominationHashP h_denom_pub; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_denomination_unknown_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_DenominationHashP *h_denom_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_DenominationUnknownAffirmationPS dua = { + .purpose.size = htonl (sizeof (dua)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .h_denom_pub = *h_denom_pub, + }; + + return scb (&dua.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_denomination_unknown_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_DenominationUnknownAffirmationPS dua = { + .purpose.size = htonl (sizeof (dua)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .h_denom_pub = *h_denom_pub, + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_UNKNOWN, + &dua, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it does not + * currently consider the given denomination to be valid + * for the requested operation. + */ +struct TALER_DenominationExpiredAffirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange sign this message. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + + /** + * Name of the operation that is not allowed at this time. Might NOT be 0-terminated, but is padded with 0s. + */ + char operation[8]; + + /** + * Hash of the public denomination key we do not know. + */ + struct TALER_DenominationHashP h_denom_pub; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_denomination_expired_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_DenominationHashP *h_denom_pub, + const char *op, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_DenominationExpiredAffirmationPS dua = { + .purpose.size = htonl (sizeof (dua)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .h_denom_pub = *h_denom_pub, + }; + + /* strncpy would create a compiler warning */ + GNUNET_memcpy (dua.operation, + op, + GNUNET_MIN (sizeof (dua.operation), + strlen (op))); + return scb (&dua.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_denomination_expired_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_DenominationHashP *h_denom_pub, + const char *op, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_DenominationExpiredAffirmationPS dua = { + .purpose.size = htonl (sizeof (dua)), + .purpose.purpose = htonl ( + TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED), + .timestamp = GNUNET_TIME_timestamp_hton (timestamp), + .h_denom_pub = *h_denom_pub, + }; + + /* strncpy would create a compiler warning */ + GNUNET_memcpy (dua.operation, + op, + GNUNET_MIN (sizeof (dua.operation), + strlen (op))); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_AFFIRM_DENOM_EXPIRED, + &dua, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it has + * closed a reserve and send back the funds. + */ +struct TALER_ReserveCloseConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange initiate the wire transfer. + */ + struct GNUNET_TIME_TimestampNBO timestamp; + + /** + * How much did the exchange send? + */ + struct TALER_AmountNBO closing_amount; + + /** + * How much did the exchange charge for closing the reserve? + */ + struct TALER_AmountNBO closing_fee; + + /** + * Public key of the reserve that was closed. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Hash of the receiver's bank account. + */ + struct TALER_PaytoHashP h_payto; + + /** + * Wire transfer subject. + */ + struct TALER_WireTransferIdentifierRawP wtid; +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_reserve_closed_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *closing_amount, + const struct TALER_Amount *closing_fee, + const char *payto, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ReserveCloseConfirmationPS rcc = { + .purpose.size = htonl (sizeof (rcc)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED), + .wtid = *wtid, + .reserve_pub = *reserve_pub, + .timestamp = GNUNET_TIME_timestamp_hton (timestamp) + }; + + TALER_amount_hton (&rcc.closing_amount, + closing_amount); + TALER_amount_hton (&rcc.closing_fee, + closing_fee); + TALER_payto_hash (payto, + &rcc.h_payto); + return scb (&rcc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_reserve_closed_verify ( + struct GNUNET_TIME_Timestamp timestamp, + const struct TALER_Amount *closing_amount, + const struct TALER_Amount *closing_fee, + const char *payto, + const struct TALER_WireTransferIdentifierRawP *wtid, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ReserveCloseConfirmationPS rcc = { + .purpose.size = htonl (sizeof (rcc)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED), + .wtid = *wtid, + .reserve_pub = *reserve_pub, + .timestamp = GNUNET_TIME_timestamp_hton (timestamp) + }; + + TALER_amount_hton (&rcc.closing_amount, + closing_amount); + TALER_amount_hton (&rcc.closing_fee, + closing_fee); + TALER_payto_hash (payto, + &rcc.h_payto); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED, + &rcc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it has + * received funds deposited into a purse. + */ +struct TALER_PurseCreateDepositConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange receive the deposits. + */ + struct GNUNET_TIME_TimestampNBO exchange_time; + + /** + * When will the purse expire? + */ + struct GNUNET_TIME_TimestampNBO purse_expiration; + + /** + * How much should the purse ultimately contain. + */ + struct TALER_AmountNBO amount_without_fee; + + /** + * How much was deposited so far. + */ + struct TALER_AmountNBO total_deposited; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Hash of the contract of the purse. + */ + struct TALER_PrivateContractHashP h_contract_terms; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_purse_created_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseCreateDepositConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + TALER_amount_hton (&dc.total_deposited, + total_deposited); + return scb (&dc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_created_verify ( + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *total_deposited, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseCreateDepositConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + TALER_amount_hton (&dc.total_deposited, + total_deposited); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_CREATION, + &dc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it has + * received funds deposited into a purse. + */ +struct TALER_CoinPurseRefundConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * How much will be refunded to the purse. + */ + struct TALER_AmountNBO refunded_amount; + + /** + * How much was the refund fee. + */ + struct TALER_AmountNBO refund_fee; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_purse_refund_sign ( + TALER_ExchangeSignCallback scb, + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *refund_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_PurseContractPublicKeyP *purse_pub, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_CoinPurseRefundConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND), + .purpose.size = htonl (sizeof (dc)), + .coin_pub = *coin_pub, + .purse_pub = *purse_pub, + }; + + TALER_amount_hton (&dc.refunded_amount, + amount_without_fee); + TALER_amount_hton (&dc.refund_fee, + refund_fee); + return scb (&dc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_refund_verify ( + const struct TALER_Amount *amount_without_fee, + const struct TALER_Amount *refund_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_CoinPurseRefundConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND), + .purpose.size = htonl (sizeof (dc)), + .coin_pub = *coin_pub, + .purse_pub = *purse_pub, + }; + + TALER_amount_hton (&dc.refunded_amount, + amount_without_fee); + TALER_amount_hton (&dc.refund_fee, + refund_fee); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_REFUND, + &dc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which the exchange affirms that it has + * merged a purse into a reserve. + */ +struct TALER_PurseMergedConfirmationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When did the exchange receive the deposits. + */ + struct GNUNET_TIME_TimestampNBO exchange_time; + + /** + * When will the purse expire? + */ + struct GNUNET_TIME_TimestampNBO purse_expiration; + + /** + * How much should the purse ultimately contain. + */ + struct TALER_AmountNBO amount_without_fee; + + /** + * Public key of the purse. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Public key of the reserve. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Hash of the contract of the purse. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Hash of the provider URL hosting the reserve. + */ + struct GNUNET_HashCode h_provider_url; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_purse_merged_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *exchange_url, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseMergedConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .reserve_pub = *reserve_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + GNUNET_CRYPTO_hash (exchange_url, + strlen (exchange_url) + 1, + &dc.h_provider_url); + return scb (&dc.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_merged_verify ( + struct GNUNET_TIME_Timestamp exchange_time, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_Amount *amount_without_fee, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_ReservePublicKeyP *reserve_pub, + const char *exchange_url, + const struct TALER_ExchangePublicKeyP *pub, + const struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseMergedConfirmationPS dc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED), + .purpose.size = htonl (sizeof (dc)), + .h_contract_terms = *h_contract_terms, + .purse_pub = *purse_pub, + .reserve_pub = *reserve_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .exchange_time = GNUNET_TIME_timestamp_hton (exchange_time) + }; + + TALER_amount_hton (&dc.amount_without_fee, + amount_without_fee); + GNUNET_CRYPTO_hash (exchange_url, + strlen (exchange_url) + 1, + &dc.h_provider_url); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_CONFIRM_PURSE_MERGED, + &dc, + &sig->eddsa_signature, + &pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on a purse status + * from the exchange. + */ +struct TALER_PurseStatusPS +{ + /** + * Purpose must be #TALER_SIGNATURE_EXCHANGE_PURSE_STATUS. Signed + * by a `struct TALER_ExchangePublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time when the purse was merged, possibly 'never'. + */ + struct GNUNET_TIME_TimestampNBO merge_timestamp; + + /** + * Time when the purse was deposited last, possibly 'never'. + */ + struct GNUNET_TIME_TimestampNBO deposit_timestamp; + + /** + * Amount deposited in total in the purse without fees. + * May be possibly less than the target amount. + */ + struct TALER_AmountNBO balance; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_purse_status_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp merge_timestamp, + struct GNUNET_TIME_Timestamp deposit_timestamp, + const struct TALER_Amount *balance, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_PurseStatusPS dcs = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS), + .purpose.size = htonl (sizeof (dcs)), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp) + }; + + TALER_amount_hton (&dcs.balance, + balance); + return scb (&dcs.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_purse_status_verify ( + struct GNUNET_TIME_Timestamp merge_timestamp, + struct GNUNET_TIME_Timestamp deposit_timestamp, + const struct TALER_Amount *balance, + const struct TALER_ExchangePublicKeyP *exchange_pub, + const struct TALER_ExchangeSignatureP *exchange_sig) +{ + struct TALER_PurseStatusPS dcs = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS), + .purpose.size = htonl (sizeof (dcs)), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .deposit_timestamp = GNUNET_TIME_timestamp_hton (deposit_timestamp) + }; + + TALER_amount_hton (&dcs.balance, + balance); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_EXCHANGE_PURSE_STATUS, + &dcs, + &exchange_sig->eddsa_signature, + &exchange_pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by the exchange to affirm that the + * owner of a reserve has certain attributes. + */ +struct TALER_ExchangeAttestPS +{ + + /** + * Purpose is #TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time when the attestation was made. + */ + struct GNUNET_TIME_TimestampNBO attest_timestamp; + + /** + * Time when the attestation expires. + */ + struct GNUNET_TIME_TimestampNBO expiration_time; + + /** + * Public key of the reserve for which the attributes + * are attested. + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Hash over the attributes. + */ + struct GNUNET_HashCode h_attributes; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum TALER_ErrorCode +TALER_exchange_online_reserve_attest_details_sign ( + TALER_ExchangeSignCallback scb, + struct GNUNET_TIME_Timestamp attest_timestamp, + struct GNUNET_TIME_Timestamp expiration_time, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *attributes, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeAttestPS rap = { + .purpose.size = htonl (sizeof (rap)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS), + .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp), + .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time), + .reserve_pub = *reserve_pub + }; + + TALER_json_hash (attributes, + &rap.h_attributes); + return scb (&rap.purpose, + pub, + sig); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_online_reserve_attest_details_verify ( + struct GNUNET_TIME_Timestamp attest_timestamp, + struct GNUNET_TIME_Timestamp expiration_time, + const struct TALER_ReservePublicKeyP *reserve_pub, + const json_t *attributes, + struct TALER_ExchangePublicKeyP *pub, + struct TALER_ExchangeSignatureP *sig) +{ + struct TALER_ExchangeAttestPS rap = { + .purpose.size = htonl (sizeof (rap)), + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS), + .attest_timestamp = GNUNET_TIME_timestamp_hton (attest_timestamp), + .expiration_time = GNUNET_TIME_timestamp_hton (expiration_time), + .reserve_pub = *reserve_pub + }; + + TALER_json_hash (attributes, + &rap.h_attributes); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_EXCHANGE_RESERVE_ATTEST_DETAILS, + &rap, + &sig->eddsa_signature, + &pub->eddsa_pub)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + /* end of exchange_signatures.c */ diff --git a/src/util/extension_age_restriction.c b/src/util/extension_age_restriction.c deleted file mode 100644 index 9c2ff7397..000000000 --- a/src/util/extension_age_restriction.c +++ /dev/null @@ -1,181 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2020 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 extension_age_restriction.c - * @brief Utility functions regarding age restriction - * @author Özgür Kesim - */ -#include "platform.h" -#include "taler_util.h" -#include "taler_extensions.h" -#include "stdint.h" - - -/** - * - * @param cfg Handle to the GNUNET configuration - * @param[out] Mask for age restriction. Will be 0 if age restriction was not enabled in the config. - * @return Error if extension for age restriction was set, but age groups were - * invalid, OK otherwise. - */ -enum TALER_EXTENSION_ReturnValue -TALER_get_age_mask (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct TALER_AgeMask *mask) -{ - char *groups; - enum TALER_EXTENSION_ReturnValue ret = TALER_EXTENSION_ERROR_SYS; - - if ((GNUNET_NO == GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED")) || - (GNUNET_NO == GNUNET_CONFIGURATION_get_value_yesno (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED"))) - { - /* Age restriction is not enabled */ - mask->mask = 0; - return TALER_EXTENSION_OK; - } - - /* Age restriction is enabled, extract age groups */ - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS", - &groups)) - { - /* FIXME: log error? */ - return TALER_EXTENSION_ERROR_SYS; - } - if (groups == NULL) - { - /* No groups defined in config, return default_age_mask */ - mask->mask = TALER_EXTENSION_DEFAULT_AGE_MASK; - return TALER_EXTENSION_OK; - } - - ret = TALER_parse_age_group_string (groups, mask); - GNUNET_free (groups); - return ret; -} - - -/** - * @param groups String representation of the age groups. Must be of the form - * a:b:...:n:m - * with - * 0 < a < b <...< n < m < 32 - * @param[out] mask Bit representation of the age groups. - * @return Error if string was invalid, OK otherwise. - */ -enum TALER_EXTENSION_ReturnValue -TALER_parse_age_group_string (char *groups, - struct TALER_AgeMask *mask) -{ - enum TALER_EXTENSION_ReturnValue ret = TALER_EXTENSION_ERROR_SYS; - char *pos; - unsigned int prev = 0; - unsigned int val; - char dummy; - - while (1) - { - pos = strchr (groups, ':'); - if (NULL != pos) - { - *pos = 0; - } - - if (1 != sscanf (groups, - "%u%c", - &val, - &dummy)) - { - /* Invalid input */ - mask->mask = 0; - ret = TALER_EXTENSION_ERROR_PARSING; - break; - } - else if ((0 >= val) || (32 <= val) || (prev >= val)) - { - /* Invalid value */ - mask->mask = 0; - ret = TALER_EXTENSION_ERROR_INVALID; - break; - } - - /* Set the corresponding bit in the mask */ - mask->mask |= 1 << val; - - if (NULL == pos) - { - /* We reached the end. Mark zeroth age-group and exit. */ - mask->mask |= 1; - ret = TALER_EXTENSION_OK; - break; - } - - prev = val; - *pos = ':'; - groups = pos + 1; - } - - return ret; -} - - -/** - * @param mask Age mask - * @return String representation of the age mask, allocated by GNUNET_malloc. - * Can be used as value in the TALER config. - */ -char * -TALER_age_mask_to_string (struct TALER_AgeMask *m) -{ - uint32_t mask = m->mask; - unsigned int n = 0; - char *buf = GNUNET_malloc (32 * 3); // max characters possible - char *pos = buf; - - if (NULL == buf) - { - return buf; - } - - while (mask != 0) - { - mask >>= 1; - n++; - if (0 == (mask & 1)) - { - continue; - } - - if (n > 9) - { - *(pos++) = '0' + n / 10; - } - *(pos++) = '0' + n % 10; - - if (0 != (mask >> 1)) - { - *(pos++) = ':'; - } - } - return buf; -} - - -/* end of extension_age_restriction.c */ diff --git a/src/util/iban.c b/src/util/iban.c index efd8c4282..c2274d3cb 100644 --- a/src/util/iban.c +++ b/src/util/iban.c @@ -233,9 +233,9 @@ TALER_iban_validate (const char *iban) return GNUNET_strdup ("IBAN number too short to be valid"); if (len > 34) return GNUNET_strdup ("IBAN number too long to be valid"); - memcpy (cc, iban, 2); - memcpy (ibancpy, iban + 4, len - 4); - memcpy (ibancpy + len - 4, iban, 4); + GNUNET_memcpy (cc, iban, 2); + GNUNET_memcpy (ibancpy, iban + 4, len - 4); + GNUNET_memcpy (ibancpy + len - 4, iban, 4); ibancpy[len] = '\0'; cc_entry.code = cc; cc_entry.english = NULL; diff --git a/src/util/merchant_signatures.c b/src/util/merchant_signatures.c new file mode 100644 index 000000000..35e0b0e07 --- /dev/null +++ b/src/util/merchant_signatures.c @@ -0,0 +1,352 @@ +/* + This file is part of TALER + Copyright (C) 2020 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 merchant_signatures.c + * @brief Utility functions for Taler merchant signatures + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on a request to obtain + * the wire transfer identifier associated with a deposit. + */ +struct TALER_DepositTrackPS +{ + /** + * Purpose must be #TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the proposal data of the contract for which this deposit is made. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_merchant_deposit_sign ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + struct TALER_MerchantSignatureP *merchant_sig) +{ + struct TALER_DepositTrackPS dtp = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION), + .purpose.size = htonl (sizeof (dtp)), + .h_contract_terms = *h_contract_terms, + .h_wire = *h_wire, + .coin_pub = *coin_pub + }; + + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &dtp, + &merchant_sig->eddsa_sig); +} + + +enum GNUNET_GenericReturnValue +TALER_merchant_deposit_verify ( + const struct TALER_MerchantPublicKeyP *merchant, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_MerchantSignatureP *merchant_sig) +{ + struct TALER_DepositTrackPS tps = { + .purpose.size = htonl (sizeof (tps)), + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION), + .coin_pub = *coin_pub, + .h_contract_terms = *h_contract_terms, + .h_wire = *h_wire + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION, + &tps, + &merchant_sig->eddsa_sig, + &merchant->eddsa_pub); +} + + +/** + * @brief Format used to generate the signature on a request to refund + * a coin into the account of the customer. + */ +struct TALER_RefundRequestPS +{ + /** + * Purpose must be #TALER_SIGNATURE_MERCHANT_REFUND. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the proposal data to identify the contract + * which is being refunded. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * The coin's public key. This is the value that must have been + * signed (blindly) by the Exchange. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Merchant-generated transaction ID for the refund. + */ + uint64_t rtransaction_id GNUNET_PACKED; + + /** + * Amount to be refunded, including refund fee charged by the + * exchange to the customer. + */ + struct TALER_AmountNBO refund_amount; +}; + + +void +TALER_merchant_refund_sign ( + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + uint64_t rtransaction_id, + const struct TALER_Amount *amount, + const struct TALER_MerchantPrivateKeyP *merchant_priv, + struct TALER_MerchantSignatureP *merchant_sig) +{ + struct TALER_RefundRequestPS rr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), + .purpose.size = htonl (sizeof (rr)), + .h_contract_terms = *h_contract_terms, + .coin_pub = *coin_pub, + .rtransaction_id = GNUNET_htonll (rtransaction_id) + }; + + TALER_amount_hton (&rr.refund_amount, + amount); + GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv, + &rr, + &merchant_sig->eddsa_sig); +} + + +enum GNUNET_GenericReturnValue +TALER_merchant_refund_verify ( + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_PrivateContractHashP *h_contract_terms, + uint64_t rtransaction_id, + const struct TALER_Amount *amount, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig) +{ + struct TALER_RefundRequestPS rr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), + .purpose.size = htonl (sizeof (rr)), + .h_contract_terms = *h_contract_terms, + .coin_pub = *coin_pub, + .rtransaction_id = GNUNET_htonll (rtransaction_id) + }; + + TALER_amount_hton (&rr.refund_amount, + amount); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr, + &merchant_sig->eddsa_sig, + &merchant_pub->eddsa_pub); +} + + +/** + * @brief Information signed by the exchange's master + * key affirming the IBAN details for the exchange. + */ +struct TALER_MerchantWireDetailsPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MERCHANT_WIRE_DETAILS. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Salted hash over the account holder's payto:// URL and + * the salt, as done by #TALER_merchant_wire_signature_hash(). + */ + struct TALER_MerchantWireHashP h_wire_details GNUNET_PACKED; + +}; + + +enum GNUNET_GenericReturnValue +TALER_merchant_wire_signature_check ( + const char *payto_uri, + const struct TALER_WireSaltP *salt, + const struct TALER_MerchantPublicKeyP *merch_pub, + const struct TALER_MerchantSignatureP *merch_sig) +{ + struct TALER_MerchantWireDetailsPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS), + .purpose.size = htonl (sizeof (wd)) + }; + + TALER_merchant_wire_signature_hash (payto_uri, + salt, + &wd.h_wire_details); + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS, + &wd, + &merch_sig->eddsa_sig, + &merch_pub->eddsa_pub); +} + + +void +TALER_merchant_wire_signature_make ( + const char *payto_uri, + const struct TALER_WireSaltP *salt, + const struct TALER_MerchantPrivateKeyP *merch_priv, + struct TALER_MerchantSignatureP *merch_sig) +{ + struct TALER_MerchantWireDetailsPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_WIRE_DETAILS), + .purpose.size = htonl (sizeof (wd)) + }; + + TALER_merchant_wire_signature_hash (payto_uri, + salt, + &wd.h_wire_details); + GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv, + &wd, + &merch_sig->eddsa_sig); +} + + +/** + * Used by merchants to return signed responses to /pay requests. + * Currently only used to return 200 OK signed responses. + */ +struct TALER_PaymentResponsePS +{ + /** + * Set to #TALER_SIGNATURE_MERCHANT_PAYMENT_OK. Note that + * unsuccessful payments are usually proven by some exchange's signature. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the proposal data associated with this confirmation + */ + struct TALER_PrivateContractHashP h_contract_terms; +}; + +void +TALER_merchant_pay_sign ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantPrivateKeyP *merch_priv, + struct TALER_MerchantSignatureP *merch_sig) +{ + struct TALER_PaymentResponsePS mr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK), + .purpose.size = htonl (sizeof (mr)), + .h_contract_terms = *h_contract_terms + }; + + GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv, + &mr, + &merch_sig->eddsa_sig); +} + + +enum GNUNET_GenericReturnValue +TALER_merchant_pay_verify ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantPublicKeyP *merchant_pub, + const struct TALER_MerchantSignatureP *merchant_sig) +{ + struct TALER_PaymentResponsePS pr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK), + .purpose.size = htonl (sizeof (pr)), + .h_contract_terms = *h_contract_terms + }; + + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_PAYMENT_OK, + &pr, + &merchant_sig->eddsa_sig, + &merchant_pub->eddsa_pub); +} + + +/** + * The contract sent by the merchant to the wallet. + */ +struct TALER_ProposalDataPS +{ + /** + * Purpose header for the signature over the proposal data + * with purpose #TALER_SIGNATURE_MERCHANT_CONTRACT. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the JSON contract in UTF-8 including 0-termination, + * using JSON_COMPACT | JSON_SORT_KEYS + */ + struct TALER_PrivateContractHashP hash; +}; + +void +TALER_merchant_contract_sign ( + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_MerchantPrivateKeyP *merch_priv, + struct GNUNET_CRYPTO_EddsaSignature *merch_sig) +{ + struct TALER_ProposalDataPS pdps = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT), + .purpose.size = htonl (sizeof (pdps)), + .hash = *h_contract_terms + }; + + GNUNET_CRYPTO_eddsa_sign (&merch_priv->eddsa_priv, + &pdps, + merch_sig); +} + + +// NB: "TALER_merchant_contract_verify" not (yet?) needed / not defined. + +/* end of merchant_signatures.c */ diff --git a/src/util/offline_signatures.c b/src/util/offline_signatures.c index fe502c659..fbff850df 100644 --- a/src/util/offline_signatures.c +++ b/src/util/offline_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020, 2021 Taler Systems SA + Copyright (C) 2020-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 @@ -23,18 +23,144 @@ #include "taler_signatures.h" +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the information of + * an AML officer status change. + */ +struct TALER_MasterAmlOfficerStatusPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_AML_KEY. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the change. + */ + struct GNUNET_TIME_TimestampNBO change_date; + + /** + * Public key of the AML officer. + */ + struct TALER_AmlOfficerPublicKeyP officer_pub; + + /** + * Hash over the AML officer's name. + */ + struct GNUNET_HashCode h_officer_name GNUNET_PACKED; + + /** + * Bitmask: 1 if enabled; 2 for read-only access. in NBO. + */ + uint32_t is_active GNUNET_PACKED; +}; +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_offline_aml_officer_status_sign ( + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *officer_name, + struct GNUNET_TIME_Timestamp change_date, + bool is_active, + bool read_only, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterAmlOfficerStatusPS as = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY), + .purpose.size = htonl (sizeof (as)), + .change_date = GNUNET_TIME_timestamp_hton (change_date), + .officer_pub = *officer_pub, + .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0)) + }; + + GNUNET_CRYPTO_hash (officer_name, + strlen (officer_name) + 1, + &as.h_officer_name); + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &as, + &master_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_offline_aml_officer_status_verify ( + const struct TALER_AmlOfficerPublicKeyP *officer_pub, + const char *officer_name, + struct GNUNET_TIME_Timestamp change_date, + bool is_active, + bool read_only, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterAmlOfficerStatusPS as = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_AML_KEY), + .purpose.size = htonl (sizeof (as)), + .change_date = GNUNET_TIME_timestamp_hton (change_date), + .officer_pub = *officer_pub, + .is_active = htonl ((is_active ? 1 : 0) + (read_only ? 2 : 0)) + }; + + GNUNET_CRYPTO_hash (officer_name, + strlen (officer_name) + 1, + &as.h_officer_name); + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_AML_KEY, + &as, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the information of + * an auditor to be added to the exchange's set of auditors. + */ +struct TALER_MasterAddAuditorPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_ADD_AUDITOR. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the change. + */ + struct GNUNET_TIME_TimestampNBO start_date; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP auditor_pub; + + /** + * Hash over the auditor's URL. + */ + struct GNUNET_HashCode h_auditor_url GNUNET_PACKED; +}; +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_auditor_add_sign ( const struct TALER_AuditorPublicKeyP *auditor_pub, const char *auditor_url, - struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Timestamp start_date, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterAddAuditorPS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_AUDITOR), .purpose.size = htonl (sizeof (kv)), - .start_date = GNUNET_TIME_absolute_hton (start_date), + .start_date = GNUNET_TIME_timestamp_hton (start_date), .auditor_pub = *auditor_pub, }; @@ -51,7 +177,7 @@ enum GNUNET_GenericReturnValue TALER_exchange_offline_auditor_add_verify ( const struct TALER_AuditorPublicKeyP *auditor_pub, const char *auditor_url, - struct GNUNET_TIME_Absolute start_date, + struct GNUNET_TIME_Timestamp start_date, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -59,7 +185,7 @@ TALER_exchange_offline_auditor_add_verify ( .purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_ADD_AUDITOR), .purpose.size = htonl (sizeof (aa)), - .start_date = GNUNET_TIME_absolute_hton (start_date), + .start_date = GNUNET_TIME_timestamp_hton (start_date), .auditor_pub = *auditor_pub }; @@ -73,17 +199,46 @@ TALER_exchange_offline_auditor_add_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the information of + * an auditor to be removed from the exchange's set of auditors. + */ +struct TALER_MasterDelAuditorPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_DEL_AUDITOR. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the change. + */ + struct GNUNET_TIME_TimestampNBO end_date; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP auditor_pub; + +}; +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_auditor_del_sign ( const struct TALER_AuditorPublicKeyP *auditor_pub, - struct GNUNET_TIME_Absolute end_date, + struct GNUNET_TIME_Timestamp end_date, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterDelAuditorPS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_AUDITOR), .purpose.size = htonl (sizeof (kv)), - .end_date = GNUNET_TIME_absolute_hton (end_date), + .end_date = GNUNET_TIME_timestamp_hton (end_date), .auditor_pub = *auditor_pub, }; @@ -96,7 +251,7 @@ TALER_exchange_offline_auditor_del_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_auditor_del_verify ( const struct TALER_AuditorPublicKeyP *auditor_pub, - struct GNUNET_TIME_Absolute end_date, + struct GNUNET_TIME_Timestamp end_date, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -104,7 +259,7 @@ TALER_exchange_offline_auditor_del_verify ( .purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_DEL_AUDITOR), .purpose.size = htonl (sizeof (da)), - .end_date = GNUNET_TIME_absolute_hton (end_date), + .end_date = GNUNET_TIME_timestamp_hton (end_date), .auditor_pub = *auditor_pub }; @@ -115,9 +270,31 @@ TALER_exchange_offline_auditor_del_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Message confirming that a denomination key was revoked. + */ +struct TALER_MasterDenominationKeyRevocationPS +{ + /** + * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_REVOKED. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the denomination key. + */ + struct TALER_DenominationHashP h_denom_pub; + +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_denomination_revoke_sign ( - const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { @@ -135,7 +312,7 @@ TALER_exchange_offline_denomination_revoke_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_denomination_revoke_verify ( - const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -154,6 +331,28 @@ TALER_exchange_offline_denomination_revoke_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Message confirming that an exchange online signing key was revoked. + */ +struct TALER_MasterSigningKeyRevocationPS +{ + /** + * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_REVOKED. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * The exchange's public key. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_signkey_revoke_sign ( const struct TALER_ExchangePublicKeyP *exchange_pub, @@ -194,12 +393,61 @@ TALER_exchange_offline_signkey_revoke_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Information about a signing key of the exchange. Signing keys are used + * to sign exchange messages other than coins, i.e. to confirm that a + * deposit was successful or that a refresh was accepted. + */ +struct TALER_ExchangeSigningKeyValidityPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When does this signing key begin to be valid? + */ + struct GNUNET_TIME_TimestampNBO start; + + /** + * When does this signing key expire? Note: This is currently when + * the Exchange will definitively stop using it. Signatures made with + * the key remain valid until @e end. When checking validity periods, + * clients should allow for some overlap between keys and tolerate + * the use of either key during the overlap time (due to the + * possibility of clock skew). + */ + struct GNUNET_TIME_TimestampNBO expire; + + /** + * When do signatures with this signing key become invalid? After + * this point, these signatures cannot be used in (legal) disputes + * anymore, as the Exchange is then allowed to destroy its side of the + * evidence. @e end is expected to be significantly larger than @e + * expire (by a year or more). + */ + struct GNUNET_TIME_TimestampNBO end; + + /** + * The public online signing key that the exchange will use + * between @e start and @e expire. + */ + struct TALER_ExchangePublicKeyP signkey_pub; +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_signkey_validity_sign ( const struct TALER_ExchangePublicKeyP *exchange_pub, - struct GNUNET_TIME_Absolute start_sign, - struct GNUNET_TIME_Absolute end_sign, - struct GNUNET_TIME_Absolute end_legal, + struct GNUNET_TIME_Timestamp start_sign, + struct GNUNET_TIME_Timestamp end_sign, + struct GNUNET_TIME_Timestamp end_legal, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { @@ -207,9 +455,9 @@ TALER_exchange_offline_signkey_validity_sign ( .purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY), .purpose.size = htonl (sizeof (skv)), - .start = GNUNET_TIME_absolute_hton (start_sign), - .expire = GNUNET_TIME_absolute_hton (end_sign), - .end = GNUNET_TIME_absolute_hton (end_legal), + .start = GNUNET_TIME_timestamp_hton (start_sign), + .expire = GNUNET_TIME_timestamp_hton (end_sign), + .end = GNUNET_TIME_timestamp_hton (end_legal), .signkey_pub = *exchange_pub }; @@ -222,9 +470,9 @@ TALER_exchange_offline_signkey_validity_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_signkey_validity_verify ( const struct TALER_ExchangePublicKeyP *exchange_pub, - struct GNUNET_TIME_Absolute start_sign, - struct GNUNET_TIME_Absolute end_sign, - struct GNUNET_TIME_Absolute end_legal, + struct GNUNET_TIME_Timestamp start_sign, + struct GNUNET_TIME_Timestamp end_sign, + struct GNUNET_TIME_Timestamp end_legal, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -232,9 +480,9 @@ TALER_exchange_offline_signkey_validity_verify ( .purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY), .purpose.size = htonl (sizeof (skv)), - .start = GNUNET_TIME_absolute_hton (start_sign), - .expire = GNUNET_TIME_absolute_hton (end_sign), - .end = GNUNET_TIME_absolute_hton (end_legal), + .start = GNUNET_TIME_timestamp_hton (start_sign), + .expire = GNUNET_TIME_timestamp_hton (end_sign), + .end = GNUNET_TIME_timestamp_hton (end_legal), .signkey_pub = *exchange_pub }; @@ -247,18 +495,100 @@ TALER_exchange_offline_signkey_validity_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Information about a denomination key. Denomination keys + * are used to sign coins of a certain value into existence. + */ +struct TALER_DenominationKeyValidityPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * The long-term offline master key of the exchange that was + * used to create @e signature. + * + * Note: This member is not strictly required, but here for + * backwards-compatibility. If we ever again badly break + * compatibility, we might want to remove it. + */ + struct TALER_MasterPublicKeyP master; + + /** + * Start time of the validity period for this key. + */ + struct GNUNET_TIME_TimestampNBO start; + + /** + * The exchange will sign fresh coins between @e start and this time. + * @e expire_withdraw will be somewhat larger than @e start to + * ensure a sufficiently large anonymity set, while also allowing + * the Exchange to limit the financial damage in case of a key being + * compromised. Thus, exchanges with low volume are expected to have a + * longer withdraw period (@e expire_withdraw - @e start) than exchanges + * with high transaction volume. The period may also differ between + * types of coins. A exchange may also have a few denomination keys + * with the same value with overlapping validity periods, to address + * issues such as clock skew. + */ + struct GNUNET_TIME_TimestampNBO expire_withdraw; + + /** + * Coins signed with the denomination key must be spent or refreshed + * between @e start and this expiration time. After this time, the + * exchange will refuse transactions involving this key as it will + * "drop" the table with double-spending information (shortly after) + * this time. Note that wallets should refresh coins significantly + * before this time to be on the safe side. @e expire_deposit must be + * significantly larger than @e expire_withdraw (by months or even + * years). + */ + struct GNUNET_TIME_TimestampNBO expire_deposit; + + /** + * When do signatures with this denomination key become invalid? + * After this point, these signatures cannot be used in (legal) + * disputes anymore, as the Exchange is then allowed to destroy its side + * of the evidence. @e expire_legal is expected to be significantly + * larger than @e expire_deposit (by a year or more). + */ + struct GNUNET_TIME_TimestampNBO expire_legal; + + /** + * The value of the coins signed with this denomination key. + */ + struct TALER_AmountNBO value; + + /** + * Fees for the coin. + */ + struct TALER_DenomFeeSetNBOP fees; + + /** + * Hash code of the denomination public key. (Used to avoid having + * the variable-size RSA key in this struct.) + */ + struct TALER_DenominationHashP denom_hash GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_denom_validity_sign ( - const struct TALER_DenominationHash *h_denom_pub, - struct GNUNET_TIME_Absolute stamp_start, - struct GNUNET_TIME_Absolute stamp_expire_withdraw, - struct GNUNET_TIME_Absolute stamp_expire_deposit, - struct GNUNET_TIME_Absolute stamp_expire_legal, + const struct TALER_DenominationHashP *h_denom_pub, + struct GNUNET_TIME_Timestamp stamp_start, + struct GNUNET_TIME_Timestamp stamp_expire_withdraw, + struct GNUNET_TIME_Timestamp stamp_expire_deposit, + struct GNUNET_TIME_Timestamp stamp_expire_legal, const struct TALER_Amount *coin_value, - const struct TALER_Amount *fee_withdraw, - const struct TALER_Amount *fee_deposit, - const struct TALER_Amount *fee_refresh, - const struct TALER_Amount *fee_refund, + const struct TALER_DenomFeeSet *fees, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { @@ -267,10 +597,10 @@ TALER_exchange_offline_denom_validity_sign ( = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY), .purpose.size = htonl (sizeof (issue)), - .start = GNUNET_TIME_absolute_hton (stamp_start), - .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), - .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .start = GNUNET_TIME_timestamp_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal), .denom_hash = *h_denom_pub }; @@ -278,14 +608,8 @@ TALER_exchange_offline_denom_validity_sign ( &issue.master.eddsa_pub); TALER_amount_hton (&issue.value, coin_value); - TALER_amount_hton (&issue.fee_withdraw, - fee_withdraw); - TALER_amount_hton (&issue.fee_deposit, - fee_deposit); - TALER_amount_hton (&issue.fee_refresh, - fee_refresh); - TALER_amount_hton (&issue.fee_refund, - fee_refund); + TALER_denom_fee_set_hton (&issue.fees, + fees); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &issue, &master_sig->eddsa_signature); @@ -294,16 +618,13 @@ TALER_exchange_offline_denom_validity_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_denom_validity_verify ( - const struct TALER_DenominationHash *h_denom_pub, - struct GNUNET_TIME_Absolute stamp_start, - struct GNUNET_TIME_Absolute stamp_expire_withdraw, - struct GNUNET_TIME_Absolute stamp_expire_deposit, - struct GNUNET_TIME_Absolute stamp_expire_legal, + const struct TALER_DenominationHashP *h_denom_pub, + struct GNUNET_TIME_Timestamp stamp_start, + struct GNUNET_TIME_Timestamp stamp_expire_withdraw, + struct GNUNET_TIME_Timestamp stamp_expire_deposit, + struct GNUNET_TIME_Timestamp stamp_expire_legal, const struct TALER_Amount *coin_value, - const struct TALER_Amount *fee_withdraw, - const struct TALER_Amount *fee_deposit, - const struct TALER_Amount *fee_refresh, - const struct TALER_Amount *fee_refund, + const struct TALER_DenomFeeSet *fees, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -312,23 +633,17 @@ TALER_exchange_offline_denom_validity_verify ( TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY), .purpose.size = htonl (sizeof (dkv)), .master = *master_pub, - .start = GNUNET_TIME_absolute_hton (stamp_start), - .expire_withdraw = GNUNET_TIME_absolute_hton (stamp_expire_withdraw), - .expire_deposit = GNUNET_TIME_absolute_hton (stamp_expire_deposit), - .expire_legal = GNUNET_TIME_absolute_hton (stamp_expire_legal), + .start = GNUNET_TIME_timestamp_hton (stamp_start), + .expire_withdraw = GNUNET_TIME_timestamp_hton (stamp_expire_withdraw), + .expire_deposit = GNUNET_TIME_timestamp_hton (stamp_expire_deposit), + .expire_legal = GNUNET_TIME_timestamp_hton (stamp_expire_legal), .denom_hash = *h_denom_pub }; TALER_amount_hton (&dkv.value, coin_value); - TALER_amount_hton (&dkv.fee_withdraw, - fee_withdraw); - TALER_amount_hton (&dkv.fee_deposit, - fee_deposit); - TALER_amount_hton (&dkv.fee_refresh, - fee_refresh); - TALER_amount_hton (&dkv.fee_refund, - fee_refund); + TALER_denom_fee_set_hton (&dkv.fees, + fees); return GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY, @@ -338,23 +653,77 @@ TALER_exchange_offline_denom_validity_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the information of + * a payto:// URI to be added to the exchange's set of active wire accounts. + */ +struct TALER_MasterAddWirePS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_ADD_WIRE. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the change. + */ + struct GNUNET_TIME_TimestampNBO start_date; + + /** + * Hash over the exchange's payto URI. + */ + struct TALER_PaytoHashP h_payto GNUNET_PACKED; + + /** + * Hash over the conversion URL, all zeros if there + * is no conversion URL. + */ + struct GNUNET_HashCode h_conversion_url; + + /** + * Hash over the debit restrictions. + */ + struct GNUNET_HashCode h_debit_restrictions; + + /** + * Hash over the credit restrictions. + */ + struct GNUNET_HashCode h_credit_restrictions; +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_wire_add_sign ( const char *payto_uri, - struct GNUNET_TIME_Absolute now, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + struct GNUNET_TIME_Timestamp now, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterAddWirePS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE), .purpose.size = htonl (sizeof (kv)), - .start_date = GNUNET_TIME_absolute_hton (now), + .start_date = GNUNET_TIME_timestamp_hton (now), }; - GNUNET_assert (GNUNET_OK == - GNUNET_TIME_round_abs (&now)); TALER_payto_hash (payto_uri, &kv.h_payto); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url) + 1, + &kv.h_conversion_url); + TALER_json_hash (debit_restrictions, + &kv.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &kv.h_credit_restrictions); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &kv, &master_sig->eddsa_signature); @@ -364,18 +733,29 @@ TALER_exchange_offline_wire_add_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_wire_add_verify ( const char *payto_uri, - struct GNUNET_TIME_Absolute sign_time, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + struct GNUNET_TIME_Timestamp sign_time, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterAddWirePS aw = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_ADD_WIRE), .purpose.size = htonl (sizeof (aw)), - .start_date = GNUNET_TIME_absolute_hton (sign_time), + .start_date = GNUNET_TIME_timestamp_hton (sign_time), }; TALER_payto_hash (payto_uri, &aw.h_payto); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url) + 1, + &aw.h_conversion_url); + TALER_json_hash (debit_restrictions, + &aw.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &aw.h_credit_restrictions); return GNUNET_CRYPTO_eddsa_verify ( TALER_SIGNATURE_MASTER_ADD_WIRE, @@ -385,21 +765,49 @@ TALER_exchange_offline_wire_add_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the information of + * a wire method to be removed to the exchange's set of active accounts. + */ +struct TALER_MasterDelWirePS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_DEL_WIRE. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time of the change. + */ + struct GNUNET_TIME_TimestampNBO end_date; + + /** + * Hash over the exchange's payto URI. + */ + struct TALER_PaytoHashP h_payto GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_wire_del_sign ( const char *payto_uri, - struct GNUNET_TIME_Absolute now, + struct GNUNET_TIME_Timestamp now, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterDelWirePS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DEL_WIRE), .purpose.size = htonl (sizeof (kv)), - .end_date = GNUNET_TIME_absolute_hton (now), + .end_date = GNUNET_TIME_timestamp_hton (now), }; - GNUNET_assert (GNUNET_OK == - GNUNET_TIME_round_abs (&now)); TALER_payto_hash (payto_uri, &kv.h_payto); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, @@ -411,7 +819,7 @@ TALER_exchange_offline_wire_del_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_wire_del_verify ( const char *payto_uri, - struct GNUNET_TIME_Absolute sign_time, + struct GNUNET_TIME_Timestamp sign_time, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { @@ -419,7 +827,7 @@ TALER_exchange_offline_wire_del_verify ( .purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_DEL_WIRE), .purpose.size = htonl (sizeof (aw)), - .end_date = GNUNET_TIME_absolute_hton (sign_time), + .end_date = GNUNET_TIME_timestamp_hton (sign_time), }; TALER_payto_hash (payto_uri, @@ -432,30 +840,69 @@ TALER_exchange_offline_wire_del_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Information signed by the exchange's master + * key stating the wire fee to be paid per wire transfer. + */ +struct TALER_MasterWireFeePS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_WIRE_FEES. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the wire method (yes, H("x-taler-bank") or H("iban")), in lower + * case, including 0-terminator. Used to uniquely identify which + * wire method these fees apply to. + */ + struct GNUNET_HashCode h_wire_method; + + /** + * Start date when the fee goes into effect. + */ + struct GNUNET_TIME_TimestampNBO start_date; + + /** + * End date when the fee stops being in effect (exclusive) + */ + struct GNUNET_TIME_TimestampNBO end_date; + + /** + * Fees charged for wire transfers using the + * given wire method. + */ + struct TALER_WireFeeSetNBOP fees; + +}; + +GNUNET_NETWORK_STRUCT_END + + void TALER_exchange_offline_wire_fee_sign ( const char *payment_method, - struct GNUNET_TIME_Absolute start_time, - struct GNUNET_TIME_Absolute end_time, - const struct TALER_Amount *wire_fee, - const struct TALER_Amount *closing_fee, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + const struct TALER_WireFeeSet *fees, const struct TALER_MasterPrivateKeyP *master_priv, struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterWireFeePS kv = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES), .purpose.size = htonl (sizeof (kv)), - .start_date = GNUNET_TIME_absolute_hton (start_time), - .end_date = GNUNET_TIME_absolute_hton (end_time), + .start_date = GNUNET_TIME_timestamp_hton (start_time), + .end_date = GNUNET_TIME_timestamp_hton (end_time), }; GNUNET_CRYPTO_hash (payment_method, strlen (payment_method) + 1, &kv.h_wire_method); - TALER_amount_hton (&kv.wire_fee, - wire_fee); - TALER_amount_hton (&kv.closing_fee, - closing_fee); + TALER_wire_fee_set_hton (&kv.fees, + fees); GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, &kv, &master_sig->eddsa_signature); @@ -465,27 +912,24 @@ TALER_exchange_offline_wire_fee_sign ( enum GNUNET_GenericReturnValue TALER_exchange_offline_wire_fee_verify ( const char *payment_method, - struct GNUNET_TIME_Absolute start_time, - struct GNUNET_TIME_Absolute end_time, - const struct TALER_Amount *wire_fee, - const struct TALER_Amount *closing_fee, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + const struct TALER_WireFeeSet *fees, const struct TALER_MasterPublicKeyP *master_pub, const struct TALER_MasterSignatureP *master_sig) { struct TALER_MasterWireFeePS wf = { .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_FEES), .purpose.size = htonl (sizeof (wf)), - .start_date = GNUNET_TIME_absolute_hton (start_time), - .end_date = GNUNET_TIME_absolute_hton (end_time) + .start_date = GNUNET_TIME_timestamp_hton (start_time), + .end_date = GNUNET_TIME_timestamp_hton (end_time) }; GNUNET_CRYPTO_hash (payment_method, strlen (payment_method) + 1, &wf.h_wire_method); - TALER_amount_hton (&wf.wire_fee, - wire_fee); - TALER_amount_hton (&wf.closing_fee, - closing_fee); + TALER_wire_fee_set_hton (&wf.fees, + fees); return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_FEES, &wf, @@ -494,4 +938,451 @@ TALER_exchange_offline_wire_fee_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Global fees charged by the exchange independent of + * denomination or wire method. + */ +struct TALER_MasterGlobalFeePS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_GLOBAL_FEES. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Start date when the fee goes into effect. + */ + struct GNUNET_TIME_TimestampNBO start_date; + + /** + * End date when the fee stops being in effect (exclusive) + */ + struct GNUNET_TIME_TimestampNBO end_date; + + /** + * How long does an exchange keep a purse around after a purse + * has expired (or been successfully merged)? A 'GET' request + * for a purse will succeed until the purse expiration time + * plus this value. + */ + struct GNUNET_TIME_RelativeNBO purse_timeout; + + /** + * How long will the exchange preserve the account history? After an + * account was deleted/closed, the exchange will retain the account history + * for legal reasons until this time. + */ + struct GNUNET_TIME_RelativeNBO history_expiration; + + /** + * Fee charged to the merchant per wire transfer. + */ + struct TALER_GlobalFeeSetNBOP fees; + + /** + * Number of concurrent purses that any + * account holder is allowed to create without having + * to pay the @e purse_fee. Here given in NBO. + */ + uint32_t purse_account_limit; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_offline_global_fee_sign ( + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + const struct TALER_GlobalFeeSet *fees, + struct GNUNET_TIME_Relative purse_timeout, + struct GNUNET_TIME_Relative history_expiration, + uint32_t purse_account_limit, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterGlobalFeePS wf = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES), + .purpose.size = htonl (sizeof (wf)), + .start_date = GNUNET_TIME_timestamp_hton (start_time), + .end_date = GNUNET_TIME_timestamp_hton (end_time), + .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout), + .history_expiration = GNUNET_TIME_relative_hton (history_expiration), + .purse_account_limit = htonl (purse_account_limit) + }; + + TALER_global_fee_set_hton (&wf.fees, + fees); + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &wf, + &master_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_offline_global_fee_verify ( + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Timestamp end_time, + const struct TALER_GlobalFeeSet *fees, + struct GNUNET_TIME_Relative purse_timeout, + struct GNUNET_TIME_Relative history_expiration, + uint32_t purse_account_limit, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterGlobalFeePS wf = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_GLOBAL_FEES), + .purpose.size = htonl (sizeof (wf)), + .start_date = GNUNET_TIME_timestamp_hton (start_time), + .end_date = GNUNET_TIME_timestamp_hton (end_time), + .purse_timeout = GNUNET_TIME_relative_hton (purse_timeout), + .history_expiration = GNUNET_TIME_relative_hton (history_expiration), + .purse_account_limit = htonl (purse_account_limit) + }; + + TALER_global_fee_set_hton (&wf.fees, + fees); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_GLOBAL_FEES, + &wf, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Signature made by the exchange offline key over the manifest of + * an extension. + */ +struct TALER_MasterExtensionManifestPS +{ + /** + * Purpose is #TALER_SIGNATURE_MASTER_EXTENSION. Signed + * by a `struct TALER_MasterPublicKeyP` using EdDSA. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the JSON object that represents the manifests of extensions. + */ + struct TALER_ExtensionManifestsHashP h_manifest GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_offline_extension_manifests_hash_sign ( + const struct TALER_ExtensionManifestsHashP *h_manifest, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterExtensionManifestPS ec = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), + .purpose.size = htonl (sizeof(ec)), + .h_manifest = *h_manifest + }; + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &ec, + &master_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_offline_extension_manifests_hash_verify ( + const struct TALER_ExtensionManifestsHashP *h_manifest, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig + ) +{ + struct TALER_MasterExtensionManifestPS ec = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), + .purpose.size = htonl (sizeof(ec)), + .h_manifest = *h_manifest + }; + + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_EXTENSION, + &ec, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Information signed by the exchange's master + * key affirming the IBAN details for the exchange. + */ +struct TALER_MasterWireDetailsPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_WIRE_DETAILS. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the account holder's payto:// URL. + */ + struct TALER_PaytoHashP h_wire_details GNUNET_PACKED; + + /** + * Hash over the conversion URL, all zeros if there + * is no conversion URL. + */ + struct GNUNET_HashCode h_conversion_url; + + /** + * Hash over the debit restrictions. + */ + struct GNUNET_HashCode h_debit_restrictions; + + /** + * Hash over the credit restrictions. + */ + struct GNUNET_HashCode h_credit_restrictions; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum GNUNET_GenericReturnValue +TALER_exchange_wire_signature_check ( + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterWireDetailsPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS), + .purpose.size = htonl (sizeof (wd)) + }; + + TALER_payto_hash (payto_uri, + &wd.h_wire_details); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url) + 1, + &wd.h_conversion_url); + TALER_json_hash (debit_restrictions, + &wd.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &wd.h_credit_restrictions); + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_WIRE_DETAILS, + &wd, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +void +TALER_exchange_wire_signature_make ( + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_MasterWireDetailsPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_WIRE_DETAILS), + .purpose.size = htonl (sizeof (wd)) + }; + + TALER_payto_hash (payto_uri, + &wd.h_wire_details); + if (NULL != conversion_url) + GNUNET_CRYPTO_hash (conversion_url, + strlen (conversion_url) + 1, + &wd.h_conversion_url); + TALER_json_hash (debit_restrictions, + &wd.h_debit_restrictions); + TALER_json_hash (credit_restrictions, + &wd.h_credit_restrictions); + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &wd, + &master_sig->eddsa_signature); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by account to merge a purse into a reserve. + */ +struct TALER_PartnerConfigurationPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_PARNTER_DETAILS + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct TALER_MasterPublicKeyP partner_pub; + struct GNUNET_TIME_TimestampNBO start_date; + struct GNUNET_TIME_TimestampNBO end_date; + struct GNUNET_TIME_RelativeNBO wad_frequency; + struct TALER_AmountNBO wad_fee; + struct GNUNET_HashCode h_url; +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_offline_partner_details_sign ( + const struct TALER_MasterPublicKeyP *partner_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_PartnerConfigurationPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS), + .purpose.size = htonl (sizeof (wd)), + .partner_pub = *partner_pub, + .start_date = GNUNET_TIME_timestamp_hton (start_date), + .end_date = GNUNET_TIME_timestamp_hton (end_date), + .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency), + }; + + GNUNET_CRYPTO_hash (partner_base_url, + strlen (partner_base_url) + 1, + &wd.h_url); + TALER_amount_hton (&wd.wad_fee, + wad_fee); + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &wd, + &master_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_offline_partner_details_verify ( + const struct TALER_MasterPublicKeyP *partner_pub, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + struct GNUNET_TIME_Relative wad_frequency, + const struct TALER_Amount *wad_fee, + const char *partner_base_url, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_PartnerConfigurationPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_PARTNER_DETAILS), + .purpose.size = htonl (sizeof (wd)), + .partner_pub = *partner_pub, + .start_date = GNUNET_TIME_timestamp_hton (start_date), + .end_date = GNUNET_TIME_timestamp_hton (end_date), + .wad_frequency = GNUNET_TIME_relative_hton (wad_frequency), + }; + + GNUNET_CRYPTO_hash (partner_base_url, + strlen (partner_base_url) + 1, + &wd.h_url); + TALER_amount_hton (&wd.wad_fee, + wad_fee); + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_PARTNER_DETAILS, + &wd, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by account to drain profits + * from the escrow account of the exchange. + */ +struct TALER_DrainProfitPS +{ + + /** + * Purpose is #TALER_SIGNATURE_MASTER_DRAIN_PROFITS + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct TALER_WireTransferIdentifierRawP wtid; + struct GNUNET_TIME_TimestampNBO date; + struct TALER_AmountNBO amount; + struct GNUNET_HashCode h_section; + struct TALER_PaytoHashP h_payto; +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_exchange_offline_profit_drain_sign ( + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Timestamp date, + const struct TALER_Amount *amount, + const char *account_section, + const char *payto_uri, + const struct TALER_MasterPrivateKeyP *master_priv, + struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_DrainProfitPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT), + .purpose.size = htonl (sizeof (wd)), + .wtid = *wtid, + .date = GNUNET_TIME_timestamp_hton (date), + }; + + GNUNET_CRYPTO_hash (account_section, + strlen (account_section) + 1, + &wd.h_section); + TALER_payto_hash (payto_uri, + &wd.h_payto); + TALER_amount_hton (&wd.amount, + amount); + GNUNET_CRYPTO_eddsa_sign (&master_priv->eddsa_priv, + &wd, + &master_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_offline_profit_drain_verify ( + const struct TALER_WireTransferIdentifierRawP *wtid, + struct GNUNET_TIME_Timestamp date, + const struct TALER_Amount *amount, + const char *account_section, + const char *payto_uri, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig) +{ + struct TALER_DrainProfitPS wd = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DRAIN_PROFIT), + .purpose.size = htonl (sizeof (wd)), + .wtid = *wtid, + .date = GNUNET_TIME_timestamp_hton (date), + }; + + GNUNET_CRYPTO_hash (account_section, + strlen (account_section) + 1, + &wd.h_section); + TALER_payto_hash (payto_uri, + &wd.h_payto); + TALER_amount_hton (&wd.amount, + amount); + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DRAIN_PROFIT, + &wd, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + /* end of offline_signatures.c */ diff --git a/src/util/paths.conf b/src/util/paths.conf index c1d2194d8..f34ccb41e 100644 --- a/src/util/paths.conf +++ b/src/util/paths.conf @@ -17,13 +17,13 @@ TALER_HOME = ${TALER_TEST_HOME:-${HOME:-${USERPROFILE}}} # for how these should be used. # Persistent data storage -TALER_DATA_HOME = ${XDG_DATA_HOME:-$TALER_HOME/.local/share}/taler/ +TALER_DATA_HOME = ${XDG_DATA_HOME:-${TALER_HOME}/.local/share}/taler/ # Configuration files -TALER_CONFIG_HOME = ${XDG_CONFIG_HOME:-$TALER_HOME/.config}/taler/ +TALER_CONFIG_HOME = ${XDG_CONFIG_HOME:-${TALER_HOME}/.config}/taler/ # Cached data, no big deal if lost -TALER_CACHE_HOME = ${XDG_CACHE_HOME:-$TALER_HOME/.cache}/taler/ +TALER_CACHE_HOME = ${XDG_CACHE_HOME:-${TALER_HOME}/.cache}/taler/ # Runtime data (always lost on system boot) TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/taler-system-runtime/ diff --git a/src/util/payto.c b/src/util/payto.c index 746b2624d..6092b73fd 100644 --- a/src/util/payto.c +++ b/src/util/payto.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2019-2021 Taler Systems SA + Copyright (C) 2019-2024 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 @@ -101,22 +101,40 @@ TALER_payto_get_method (const char *payto_uri) char * TALER_xtalerbank_account_from_payto (const char *payto) { + const char *host; const char *beg; + const char *nxt; const char *end; if (0 != strncasecmp (payto, PAYTO "x-taler-bank/", strlen (PAYTO "x-taler-bank/"))) + { + GNUNET_break_op (0); return NULL; - beg = strchr (&payto[strlen (PAYTO "x-taler-bank/")], + } + host = &payto[strlen (PAYTO "x-taler-bank/")]; + beg = strchr (host, '/'); if (NULL == beg) + { + GNUNET_break_op (0); return NULL; - beg++; /* now points to $ACCOUNT */ + } + beg++; /* now points to $ACCOUNT or $PATH */ + nxt = strchr (beg, + '/'); end = strchr (beg, '?'); if (NULL == end) - return GNUNET_strdup (beg); /* optional part is missing */ + end = &beg[strlen (beg)]; + while ( (NULL != nxt) && + (end - nxt > 0) ) + { + beg = nxt + 1; + nxt = strchr (beg, + '/'); + } return GNUNET_strndup (beg, end - beg); } @@ -143,7 +161,6 @@ validate_payto_iban (const char *account_url) IBAN_PREFIX, strlen (IBAN_PREFIX))) return NULL; /* not an IBAN */ - iban = strrchr (account_url, '/') + 1; #undef IBAN_PREFIX q = strchr (iban, @@ -177,6 +194,138 @@ validate_payto_iban (const char *account_url) } +/** + * Validate payto://x-taler-bank/ account URL (only account information, + * wire subject and amount are ignored). + * + * @param account_url payto URL to parse + * @return NULL on success, otherwise an error message + * to be freed by the caller + */ +static char * +validate_payto_xtalerbank (const char *account_url) +{ + const char *user; + const char *nxt; + const char *beg; + const char *end; + const char *host; + bool dot_ok; + bool post_colon; + bool port_ok; + +#define XTALERBANK_PREFIX PAYTO "x-taler-bank/" + if (0 != strncasecmp (account_url, + XTALERBANK_PREFIX, + strlen (XTALERBANK_PREFIX))) + return NULL; /* not an IBAN */ + host = &account_url[strlen (XTALERBANK_PREFIX)]; +#undef XTALERBANK_PREFIX + beg = strchr (host, + '/'); + if (NULL == beg) + { + return GNUNET_strdup ("account name missing"); + } + beg++; /* now points to $ACCOUNT or $PATH */ + nxt = strchr (beg, + '/'); + end = strchr (beg, + '?'); + if (NULL == end) + { + return GNUNET_strdup ("'receiver-name' parameter missing"); + } + while ( (NULL != nxt) && + (end - nxt > 0) ) + { + beg = nxt + 1; + nxt = strchr (beg, + '/'); + } + user = beg; + if (user == host + 1) + { + return GNUNET_strdup ("domain name missing"); + } + if ('-' == host[0]) + return GNUNET_strdup ("invalid character '-' at start of domain name"); + dot_ok = false; + post_colon = false; + port_ok = false; + while (host != user) + { + char c = host[0]; + + if ('/' == c) + { + /* path started, do not care about characters + in the path */ + break; + } + if (':' == c) + { + post_colon = true; + host++; + continue; + } + if (post_colon) + { + if (! ( ('0' <= c) && ('9' >= c) ) ) + { + char *err; + + GNUNET_asprintf (&err, + "invalid character '%c' in port", + c); + return err; + } + port_ok = true; + } + else + { + if ('.' == c) + { + if (! dot_ok) + return GNUNET_strdup ("invalid domain name (misplaced '.')"); + dot_ok = false; + } + else + { + if (! ( ('-' == c) || + ( ('0' <= c) && ('9' >= c) ) || + ( ('a' <= c) && ('z' >= c) ) || + ( ('A' <= c) && ('Z' >= c) ) ) ) + { + char *err; + + GNUNET_asprintf (&err, + "invalid character '%c' in domain name", + c); + return err; + } + dot_ok = true; + } + } + host++; + } + if (post_colon && (! port_ok) ) + { + return GNUNET_strdup ("port missing after ':'"); + } + { + char *target; + + target = payto_get_key (account_url, + "receiver-name="); + if (NULL == target) + return GNUNET_strdup ("'receiver-name' parameter missing"); + GNUNET_free (target); + } + return NULL; +} + + char * TALER_payto_validate (const char *payto_uri) { @@ -193,7 +342,7 @@ TALER_payto_validate (const char *payto_uri) /* This is more strict than RFC 8905, alas we do not need to support messages/instructions/etc., and it is generally better to start with a narrow whitelist; we can be more permissive later ...*/ #define ALLOWED_CHARACTERS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=" + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:&?-.,=+%~" if (NULL == strchr (ALLOWED_CHARACTERS, (int) payto_uri[i])) { @@ -217,6 +366,8 @@ TALER_payto_validate (const char *payto_uri) if (NULL != (ret = validate_payto_iban (payto_uri))) return ret; /* got a definitive answer */ + if (NULL != (ret = validate_payto_xtalerbank (payto_uri))) + return ret; /* got a definitive answer */ /* Insert other bank account validation methods here later! */ @@ -224,52 +375,320 @@ TALER_payto_validate (const char *payto_uri) } +char * +TALER_payto_get_receiver_name (const char *payto) +{ + char *err; + + err = TALER_payto_validate (payto); + if (NULL != err) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid payto://-URI `%s': %s\n", + payto, + err); + GNUNET_free (err); + return NULL; + } + return payto_get_key (payto, + "receiver-name="); +} + + +/** + * Normalize "payto://x-taler-bank/$HOSTNAME/[$PATH/]$USERNAME" + * URI in @a input. + * + * Converts to lower-case, except for [$PATH/]$USERNAME which + * is case-sensitive. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_x_taler_bank (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + unsigned int sc = 0; + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + if (sc < 4) + res[i] = (char) tolower ((int) c); + else + res[i] = c; + } + return res; +} + + +/** + * Normalize "payto://iban[/$BIC]/$IBAN" + * URI in @a input. + * + * Removes $BIC (if present) and converts $IBAN to upper-case and prefix to + * lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_iban (size_t len, + const char input[static len]) +{ + char *res; + size_t pos = 0; + unsigned int sc = 0; + bool have_bic; + + for (unsigned int i = 0; i<len; i++) + if ('/' == input[i]) + sc++; + if ( (sc > 4) || + (sc < 3) ) + { + GNUNET_break (0); + return NULL; + } + have_bic = (4 == sc); + res = GNUNET_malloc (len + 1); + sc = 0; + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + switch (sc) + { + case 0: /* payto: */ + case 1: /* / */ + case 2: /* /iban */ + res[pos++] = (char) tolower ((int) c); + break; + case 3: /* /$BIC or /$IBAN */ + if (have_bic) + continue; + res[pos++] = (char) toupper ((int) c); + break; + case 4: /* /$IBAN */ + res[pos++] = (char) toupper ((int) c); + break; + } + } + GNUNET_assert (pos <= len); + return res; +} + + +/** + * Normalize "payto://upi/$EMAIL" + * URI in @a input. + * + * Converts to lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_upi (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + res[i] = (char) tolower ((int) c); + } + return res; +} + + +/** + * Normalize "payto://bitcoin/$ADDRESS" + * URI in @a input. + * + * Converts to lower-case, except for $ADDRESS which + * is case-sensitive. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_bitcoin (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + unsigned int sc = 0; + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + if ('/' == c) + sc++; + if (sc < 3) + res[i] = (char) tolower ((int) c); + else + res[i] = c; + } + return res; +} + + +/** + * Normalize "payto://ilp/$NAME" + * URI in @a input. + * + * Converts to lower-case. + * + * @param len number of bytes in @a input + * @param input input URL + * @return NULL on error, otherwise 0-terminated canonicalized URI. + */ +static char * +normalize_payto_ilp (size_t len, + const char input[static len]) +{ + char *res = GNUNET_malloc (len + 1); + + for (unsigned int i = 0; i<len; i++) + { + char c = input[i]; + + res[i] = (char) tolower ((int) c); + } + return res; +} + + +char * +TALER_payto_normalize (const char *input) +{ + char *method; + const char *end; + char *ret; + + { + char *err; + + err = TALER_payto_validate (input); + if (NULL != err) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Malformed payto://-URI `%s': %s\n", + input, + err); + GNUNET_free (err); + return NULL; + } + } + method = TALER_payto_get_method (input); + if (NULL == method) + { + GNUNET_break (0); + return NULL; + } + end = strchr (input, '?'); + if (NULL == end) + end = &input[strlen (input)]; + if (0 == strcasecmp (method, + "x-taler-bank")) + ret = normalize_payto_x_taler_bank (end - input, + input); + else if (0 == strcasecmp (method, + "iban")) + ret = normalize_payto_iban (end - input, + input); + else if (0 == strcasecmp (method, + "upi")) + ret = normalize_payto_upi (end - input, + input); + else if (0 == strcasecmp (method, + "bitcoin")) + ret = normalize_payto_bitcoin (end - input, + input); + else if (0 == strcasecmp (method, + "ilp")) + ret = normalize_payto_ilp (end - input, + input); + else + ret = GNUNET_strndup (input, + end - input); + GNUNET_free (method); + return ret; +} + + void TALER_payto_hash (const char *payto, - struct TALER_PaytoHash *h_payto) + struct TALER_PaytoHashP *h_payto) { + struct GNUNET_HashCode sha512; + GNUNET_CRYPTO_hash (payto, strlen (payto) + 1, - &h_payto->hash); + &sha512); + GNUNET_static_assert (sizeof (sha512) > sizeof (*h_payto)); + /* truncate */ + GNUNET_memcpy (h_payto, + &sha512, + sizeof (*h_payto)); } char * -TALER_payto_from_reserve (const char *exchange_base_url, +TALER_reserve_make_payto (const char *exchange_url, const struct TALER_ReservePublicKeyP *reserve_pub) { - char *payto_uri; - char *rps; - unsigned int skip; - const char *extra = ""; - int url_len; - - rps = GNUNET_STRINGS_data_to_string_alloc (reserve_pub, - sizeof (*reserve_pub)); - skip = 0; - if (0 == strncasecmp (exchange_base_url, - "http://", - strlen ("http://"))) + char pub_str[sizeof (*reserve_pub) * 2]; + char *end; + bool is_http; + char *reserve_url; + + end = GNUNET_STRINGS_data_to_string ( + reserve_pub, + sizeof (*reserve_pub), + pub_str, + sizeof (pub_str)); + *end = '\0'; + if (0 == strncmp (exchange_url, + "http://", + strlen ("http://"))) + { + is_http = true; + exchange_url = &exchange_url[strlen ("http://")]; + } + else if (0 == strncmp (exchange_url, + "https://", + strlen ("https://"))) { - skip = strlen ("http://"); - extra = "+http"; + is_http = false; + exchange_url = &exchange_url[strlen ("https://")]; + } + else + { + GNUNET_break (0); + return NULL; } - if (0 == strncasecmp (exchange_base_url, - "https://", - strlen ("https://"))) - skip = strlen ("https://"); - url_len = strlen (exchange_base_url); - if ('/' == exchange_base_url[url_len - 1]) - url_len--; - url_len -= skip; - GNUNET_asprintf (&payto_uri, - "taler%s://reserve/%.*s/%s", - extra, - url_len, - exchange_base_url + skip, - rps); - GNUNET_free (rps); - return payto_uri; + /* exchange_url includes trailing '/' */ + GNUNET_asprintf (&reserve_url, + "payto://%s/%s%s", + is_http ? "taler-reserve-http" : "taler-reserve", + exchange_url, + pub_str); + return reserve_url; } diff --git a/src/util/secmod_common.c b/src/util/secmod_common.c index 0a83bfb6c..87ce17e06 100644 --- a/src/util/secmod_common.c +++ b/src/util/secmod_common.c @@ -23,7 +23,9 @@ #include "taler_signatures.h" #include "secmod_common.h" #include <poll.h> +#ifdef __linux__ #include <sys/eventfd.h> +#endif /** @@ -78,7 +80,7 @@ TES_transmit_raw (int sock, size_t end, const void *pos) { - ssize_t off = 0; + size_t off = 0; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Sending message of length %u\n", @@ -217,7 +219,11 @@ TES_wake_clients (void) client = client->next) { GNUNET_assert (sizeof (num) == +#ifdef __linux__ write (client->esock, +#else + write (client->esock_in, +#endif &num, sizeof (num))); } @@ -232,8 +238,8 @@ TES_read_work (void *cls, struct TES_Client *client = cls; char *buf = client->iobuf; size_t off = 0; - uint16_t msize; - const struct GNUNET_MessageHeader *hdr; + uint16_t msize = 0; + const struct GNUNET_MessageHeader *hdr = NULL; enum GNUNET_GenericReturnValue ret; do @@ -243,7 +249,7 @@ TES_read_work (void *cls, recv_size = recv (client->csock, &buf[off], sizeof (client->iobuf) - off, - 0); + 0); if (-1 == recv_size) { if ( (0 == off) && @@ -309,7 +315,11 @@ TES_await_ready (struct TES_Client *client) .events = POLLIN }, { +#ifdef __linux__ .fd = client->esock, +#else + .fd = client->esock_out, +#endif .events = POLLIN }, }; @@ -324,13 +334,21 @@ TES_await_ready (struct TES_Client *client) "poll"); for (int i = 0; i<2; i++) { +#ifdef __linux__ if ( (pfds[i].fd == client->esock) && +#else + if ( (pfds[i].fd == client->esock_out) && +#endif (POLLIN == pfds[i].revents) ) { uint64_t num; GNUNET_assert (sizeof (num) == +#ifdef __linux__ read (client->esock, +#else + read (client->esock_out, +#endif &num, sizeof (num))); return true; @@ -349,7 +367,12 @@ TES_free_client (struct TES_Client *client) client); GNUNET_assert (0 == pthread_mutex_unlock (&TES_clients_lock)); GNUNET_break (0 == close (client->csock)); +#ifdef __linux__ GNUNET_break (0 == close (client->esock)); +#else + GNUNET_break (0 == close (client->esock_in)); + GNUNET_break (0 == close (client->esock_out)); +#endif pthread_detach (client->worker); GNUNET_free (client); } @@ -401,7 +424,11 @@ listen_job (void *cls) { const struct TES_Callbacks *cb = cls; int s; +#ifdef __linux__ int e; +#else + int e[2]; +#endif struct sockaddr_storage sa; socklen_t sa_len = sizeof (sa); @@ -418,6 +445,7 @@ listen_job (void *cls) "accept"); return; } +#ifdef __linux__ e = eventfd (0, EFD_CLOEXEC); if (-1 == e) @@ -427,13 +455,27 @@ listen_job (void *cls) GNUNET_break (0 == close (s)); return; } +#else + if (0 != pipe (e)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "pipe"); + GNUNET_break (0 == close (s)); + return; + } +#endif { struct TES_Client *client; client = GNUNET_new (struct TES_Client); client->cb = *cb; client->csock = s; +#ifdef __linux__ client->esock = e; +#else + client->esock_in = e[1]; + client->esock_out = e[0]; +#endif GNUNET_assert (0 == pthread_mutex_lock (&TES_clients_lock)); GNUNET_CONTAINER_DLL_insert (TES_clients_head, TES_clients_tail, diff --git a/src/util/secmod_common.h b/src/util/secmod_common.h index b24e91cb2..304acebdf 100644 --- a/src/util/secmod_common.h +++ b/src/util/secmod_common.h @@ -155,10 +155,22 @@ struct TES_Client */ int csock; +#ifdef __linux__ /** * Event socket. */ int esock; +#else + /** + * Input end of the event pipe. + */ + int esock_in; + + /** + * Output end of the event pipe. + */ + int esock_out; +#endif }; diff --git a/src/util/secmod_signatures.c b/src/util/secmod_signatures.c index 077ce229a..3b539d5fe 100644 --- a/src/util/secmod_signatures.c +++ b/src/util/secmod_signatures.c @@ -23,10 +23,41 @@ #include "taler_signatures.h" +/** + * @brief format used by the signing crypto helper when affirming + * that it created an exchange signing key. + */ +struct TALER_SigningKeyAnnouncementPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_SM_SIGNING_KEY. + * Used with an EdDSA signature of a `struct TALER_SecurityModulePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Public signing key of the exchange this is about. + */ + struct TALER_ExchangePublicKeyP exchange_pub; + + /** + * When does the key become available? + */ + struct GNUNET_TIME_TimestampNBO anchor_time; + + /** + * How long is the key available after @e anchor_time? + */ + struct GNUNET_TIME_RelativeNBO duration; + +}; + + void TALER_exchange_secmod_eddsa_sign ( const struct TALER_ExchangePublicKeyP *exchange_pub, - struct GNUNET_TIME_Absolute start_sign, + struct GNUNET_TIME_Timestamp start_sign, struct GNUNET_TIME_Relative duration, const struct TALER_SecurityModulePrivateKeyP *secm_priv, struct TALER_SecurityModuleSignatureP *secm_sig) @@ -35,7 +66,7 @@ TALER_exchange_secmod_eddsa_sign ( .purpose.purpose = htonl (TALER_SIGNATURE_SM_SIGNING_KEY), .purpose.size = htonl (sizeof (ska)), .exchange_pub = *exchange_pub, - .anchor_time = GNUNET_TIME_absolute_hton (start_sign), + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), .duration = GNUNET_TIME_relative_hton (duration) }; @@ -48,7 +79,7 @@ TALER_exchange_secmod_eddsa_sign ( enum GNUNET_GenericReturnValue TALER_exchange_secmod_eddsa_verify ( const struct TALER_ExchangePublicKeyP *exchange_pub, - struct GNUNET_TIME_Absolute start_sign, + struct GNUNET_TIME_Timestamp start_sign, struct GNUNET_TIME_Relative duration, const struct TALER_SecurityModulePublicKeyP *secm_pub, const struct TALER_SecurityModuleSignatureP *secm_sig) @@ -57,7 +88,7 @@ TALER_exchange_secmod_eddsa_verify ( .purpose.purpose = htonl (TALER_SIGNATURE_SM_SIGNING_KEY), .purpose.size = htonl (sizeof (ska)), .exchange_pub = *exchange_pub, - .anchor_time = GNUNET_TIME_absolute_hton (start_sign), + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), .duration = GNUNET_TIME_relative_hton (duration) }; @@ -69,11 +100,46 @@ TALER_exchange_secmod_eddsa_verify ( } +/** + * @brief format used by the denomination crypto helper when affirming + * that it created a denomination key. + */ +struct TALER_DenominationKeyAnnouncementPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY. + * Used with an EdDSA signature of a `struct TALER_SecurityModulePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the denomination public key. + */ + struct TALER_DenominationHashP h_denom; + + /** + * Hash of the section name in the configuration of this denomination. + */ + struct GNUNET_HashCode h_section_name; + + /** + * When does the key become available? + */ + struct GNUNET_TIME_TimestampNBO anchor_time; + + /** + * How long is the key available after @e anchor_time? + */ + struct GNUNET_TIME_RelativeNBO duration_withdraw; + +}; + void TALER_exchange_secmod_rsa_sign ( const struct TALER_RsaPubHashP *h_rsa, const char *section_name, - struct GNUNET_TIME_Absolute start_sign, + struct GNUNET_TIME_Timestamp start_sign, struct GNUNET_TIME_Relative duration, const struct TALER_SecurityModulePrivateKeyP *secm_priv, struct TALER_SecurityModuleSignatureP *secm_sig) @@ -81,8 +147,8 @@ TALER_exchange_secmod_rsa_sign ( struct TALER_DenominationKeyAnnouncementPS dka = { .purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY), .purpose.size = htonl (sizeof (dka)), - .h_rsa = *h_rsa, - .anchor_time = GNUNET_TIME_absolute_hton (start_sign), + .h_denom.hash = h_rsa->hash, + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), .duration_withdraw = GNUNET_TIME_relative_hton (duration) }; @@ -100,7 +166,7 @@ enum GNUNET_GenericReturnValue TALER_exchange_secmod_rsa_verify ( const struct TALER_RsaPubHashP *h_rsa, const char *section_name, - struct GNUNET_TIME_Absolute start_sign, + struct GNUNET_TIME_Timestamp start_sign, struct GNUNET_TIME_Relative duration, const struct TALER_SecurityModulePublicKeyP *secm_pub, const struct TALER_SecurityModuleSignatureP *secm_sig) @@ -108,8 +174,8 @@ TALER_exchange_secmod_rsa_verify ( struct TALER_DenominationKeyAnnouncementPS dka = { .purpose.purpose = htonl (TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY), .purpose.size = htonl (sizeof (dka)), - .h_rsa = *h_rsa, - .anchor_time = GNUNET_TIME_absolute_hton (start_sign), + .h_denom.hash = h_rsa->hash, + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), .duration_withdraw = GNUNET_TIME_relative_hton (duration) }; @@ -124,4 +190,59 @@ TALER_exchange_secmod_rsa_verify ( } +void +TALER_exchange_secmod_cs_sign ( + const struct TALER_CsPubHashP *h_cs, + const char *section_name, + struct GNUNET_TIME_Timestamp start_sign, + struct GNUNET_TIME_Relative duration, + const struct TALER_SecurityModulePrivateKeyP *secm_priv, + struct TALER_SecurityModuleSignatureP *secm_sig) +{ + struct TALER_DenominationKeyAnnouncementPS dka = { + .purpose.purpose = htonl (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY), + .purpose.size = htonl (sizeof (dka)), + .h_denom.hash = h_cs->hash, + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), + .duration_withdraw = GNUNET_TIME_relative_hton (duration) + }; + + GNUNET_CRYPTO_hash (section_name, + strlen (section_name) + 1, + &dka.h_section_name); + GNUNET_CRYPTO_eddsa_sign (&secm_priv->eddsa_priv, + &dka, + &secm_sig->eddsa_signature); + +} + + +enum GNUNET_GenericReturnValue +TALER_exchange_secmod_cs_verify ( + const struct TALER_CsPubHashP *h_cs, + const char *section_name, + struct GNUNET_TIME_Timestamp start_sign, + struct GNUNET_TIME_Relative duration, + const struct TALER_SecurityModulePublicKeyP *secm_pub, + const struct TALER_SecurityModuleSignatureP *secm_sig) +{ + struct TALER_DenominationKeyAnnouncementPS dka = { + .purpose.purpose = htonl (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY), + .purpose.size = htonl (sizeof (dka)), + .h_denom.hash = h_cs->hash, + .anchor_time = GNUNET_TIME_timestamp_hton (start_sign), + .duration_withdraw = GNUNET_TIME_relative_hton (duration) + }; + + GNUNET_CRYPTO_hash (section_name, + strlen (section_name) + 1, + &dka.h_section_name); + return + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SM_CS_DENOMINATION_KEY, + &dka, + &secm_sig->eddsa_signature, + &secm_pub->eddsa_pub); +} + + /* end of secmod_signatures.c */ diff --git a/src/util/taler-config.in b/src/util/taler-config.in index 07f6401d6..3399aec10 100644 --- a/src/util/taler-config.in +++ b/src/util/taler-config.in @@ -7,7 +7,7 @@ if ! type gnunet-config >/dev/null; then exit 1 fi -GC=`which gnunet-config` -SO=`ls %libdir%/libtalerutil.so.* | sort -n | tail -n1` +GC=$(which gnunet-config) +SO=$(ls %libdir%/libtalerutil.so.* | sort -n | tail -n1) export LD_PRELOAD=${LD_PRELOAD:-}:${SO} exec gnunet-config "$@" diff --git a/src/util/taler-crypto-worker.c b/src/util/taler-crypto-worker.c deleted file mode 100644 index 9c49ea374..000000000 --- a/src/util/taler-crypto-worker.c +++ /dev/null @@ -1,281 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2021 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/taler-crypto-worker.c - * @brief Standalone process to perform various cryptographic operations. - * @author Florian Dold - */ -#include "platform.h" -#include "taler_util.h" -#include <gnunet/gnunet_json_lib.h> -#include <gnunet/gnunet_crypto_lib.h> -#include "taler_error_codes.h" -#include "taler_json_lib.h" -#include "taler_signatures.h" -#include "secmod_common.h" - - -/** - * Return value from main(). - */ -static int global_ret; - - -/** - * Main function that will be run under the GNUnet scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param cfg configuration - */ -static void -run (void *cls, - char *const *args, - const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - (void) cls; - (void) args; - (void) cfgfile; - - json_t *req; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "started crypto worker\n"); - - for (;;) - { - const char *op; - const json_t *args; - req = json_loadf (stdin, JSON_DISABLE_EOF_CHECK, NULL); - if (NULL == req) - { - if (feof (stdin)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "end of input\n"); - global_ret = 0; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "invalid JSON\n"); - global_ret = 1; - return; - } - op = json_string_value (json_object_get (req, - "op")); - if (! op) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "no op specified\n"); - global_ret = 1; - return; - } - args = json_object_get (req, "args"); - if (! args) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "no args specified\n"); - global_ret = 1; - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "got request\n"); - if (0 == strcmp ("eddsa_verify", - op)) - { - struct GNUNET_CRYPTO_EddsaPublicKey pub; - struct GNUNET_CRYPTO_EddsaSignature sig; - struct GNUNET_CRYPTO_EccSignaturePurpose *msg; - size_t msg_size; - enum GNUNET_GenericReturnValue verify_ret; - json_t *resp; - struct GNUNET_JSON_Specification eddsa_verify_spec[] = { - GNUNET_JSON_spec_fixed_auto ("pub", - &pub), - GNUNET_JSON_spec_fixed_auto ("sig", - &sig), - GNUNET_JSON_spec_varsize ("msg", - (void **) &msg, - &msg_size), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != GNUNET_JSON_parse (args, - eddsa_verify_spec, - NULL, - NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "malformed op args\n"); - global_ret = 1; - return; - } - verify_ret = GNUNET_CRYPTO_eddsa_verify_ ( - ntohl (msg->purpose), - msg, - &sig, - &pub); - resp = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_bool ("valid", - GNUNET_OK == verify_ret)); - json_dumpf (resp, stdout, JSON_COMPACT); - printf ("\n"); - fflush (stdout); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "sent response\n"); - continue; - } - if (0 == strcmp ("setup_refresh_planchet", op)) - { - struct TALER_DenominationPublicKey denom_pub; - struct TALER_Amount fee_withdraw; - struct TALER_Amount value; - struct TALER_ReservePublicKeyP reserve_pub; - struct TALER_ReservePublicKeyP reserve_priv; - uint32_t coin_index; - json_t *resp; - struct GNUNET_JSON_Specification eddsa_verify_spec[] = { - TALER_JSON_spec_denom_pub ("denom_pub", - &denom_pub), - TALER_JSON_spec_amount_any ("fee_withdraw", - &fee_withdraw), - TALER_JSON_spec_amount_any ("value", - &value), - GNUNET_JSON_spec_fixed_auto ("reserve_pub", - &reserve_pub), - GNUNET_JSON_spec_fixed_auto ("reserve_priv", - &reserve_priv), - GNUNET_JSON_spec_uint32 ("coin_index", - &coin_index), - GNUNET_JSON_spec_end () - }; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_PlanchetSecretsP ps; - - if (GNUNET_OK != - GNUNET_JSON_parse (args, - eddsa_verify_spec, - NULL, - NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "malformed op args\n"); - global_ret = 1; - return; - } -#if FIXME_FLORIAN - TALER_planchet_setup_refresh (&transfer_secret, - coin_num_salt, - &ps); -#endif - GNUNET_CRYPTO_eddsa_key_get_public (&ps.coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - - resp = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("coin_priv", &ps.coin_priv), - GNUNET_JSON_pack_data_auto ("coin_pub", &coin_pub), - GNUNET_JSON_pack_data_auto ("blinding_key", &ps.blinding_key) - ); - json_dumpf (resp, stdout, JSON_COMPACT); - printf ("\n"); - fflush (stdout); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "sent response\n"); - continue; - } - if (0 == strcmp (op, "create_planchet")) - { - struct TALER_TransferSecretP transfer_secret; - uint32_t coin_num_salt; - struct TALER_PlanchetSecretsP ps; - struct TALER_CoinSpendPublicKeyP coin_pub; - json_t *resp; - struct GNUNET_JSON_Specification eddsa_verify_spec[] = { - GNUNET_JSON_spec_fixed_auto ("transfer_secret", - &transfer_secret), - GNUNET_JSON_spec_uint32 ("coin_index", - &coin_num_salt), - GNUNET_JSON_spec_end () - }; - if (GNUNET_OK != GNUNET_JSON_parse (args, - eddsa_verify_spec, - NULL, - NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "malformed op args\n"); - global_ret = 1; - return; - } - TALER_planchet_setup_refresh (&transfer_secret, - coin_num_salt, &ps); - GNUNET_CRYPTO_eddsa_key_get_public (&ps.coin_priv.eddsa_priv, - &coin_pub.eddsa_pub); - - resp = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_data_auto ("coin_priv", &ps.coin_priv), - GNUNET_JSON_pack_data_auto ("coin_pub", &coin_pub), - GNUNET_JSON_pack_data_auto ("blinding_key", &ps.blinding_key) - ); - json_dumpf (resp, stdout, JSON_COMPACT); - printf ("\n"); - fflush (stdout); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "sent response\n"); - continue; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "unsupported operation '%s'\n", - op); - global_ret = 1; - return; - } - -} - - -/** - * The entry point. - * - * @param argc number of arguments in @a argv - * @param argv command-line arguments - * @return 0 on normal termination - */ -int -main (int argc, - char **argv) -{ - struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_END - }; - int ret; - - /* force linker to link against libtalerutil; if we do - not do this, the linker may "optimize" libtalerutil - away and skip #TALER_OS_init(), which we do need */ - TALER_OS_init (); - ret = GNUNET_PROGRAM_run (argc, argv, - "taler-crypto-worker", - "Execute cryptographic operations read from stdin", - options, - &run, - NULL); - if (GNUNET_NO == ret) - return 0; - if (GNUNET_SYSERR == ret) - return 1; - return global_ret; -} diff --git a/src/util/taler-exchange-secmod-cs.c b/src/util/taler-exchange-secmod-cs.c new file mode 100644 index 000000000..3e9ba1558 --- /dev/null +++ b/src/util/taler-exchange-secmod-cs.c @@ -0,0 +1,2344 @@ +/* + This file is part of TALER + Copyright (C) 2014-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/taler-exchange-secmod-cs.c + * @brief Standalone process to perform private key CS operations + * @author Christian Grothoff + * + * Key design points: + * - EVERY thread of the exchange will have its own pair of connections to the + * crypto helpers. This way, every thread will also have its own /keys state + * and avoid the need to synchronize on those. + * - auditor signatures and master signatures are to be kept in the exchange DB, + * and merged with the public keys of the helper by the exchange HTTPD! + * - the main loop of the helper is SINGLE-THREADED, but there are + * threads for crypto-workers which do the signing in parallel, one per client. + * - thread-safety: signing happens in parallel, thus when REMOVING private keys, + * we must ensure that all signers are done before we fully free() the + * private key. This is done by reference counting (as work is always + * assigned and collected by the main thread). + */ +#include "platform.h" +#include "taler_util.h" +#include "taler-exchange-secmod-cs.h" +#include <gcrypt.h> +#include <pthread.h> +#include <sys/eventfd.h> +#include "taler_error_codes.h" +#include "taler_signatures.h" +#include "secmod_common.h" +#include <poll.h> + + +/** + * Information we keep per denomination. + */ +struct Denomination; + + +/** + * One particular denomination key. + */ +struct DenominationKey +{ + + /** + * Kept in a DLL of the respective denomination. Sorted by anchor time. + */ + struct DenominationKey *next; + + /** + * Kept in a DLL of the respective denomination. Sorted by anchor time. + */ + struct DenominationKey *prev; + + /** + * Denomination this key belongs to. + */ + struct Denomination *denom; + + /** + * Name of the file this key is stored under. + */ + char *filename; + + /** + * The private key of the denomination. + */ + struct GNUNET_CRYPTO_CsPrivateKey denom_priv; + + /** + * The public key of the denomination. + */ + struct GNUNET_CRYPTO_CsPublicKey denom_pub; + + /** + * Message to transmit to clients to introduce this public key. + */ + struct TALER_CRYPTO_CsKeyAvailableNotification *an; + + /** + * Hash of this denomination's public key. + */ + struct TALER_CsPubHashP h_cs; + + /** + * Time at which this key is supposed to become valid. + */ + struct GNUNET_TIME_Timestamp anchor; + + /** + * Generation when this key was created or revoked. + */ + uint64_t key_gen; + + /** + * Reference counter. Counts the number of threads that are + * using this key at this time. + */ + unsigned int rc; + + /** + * Flag set to true if this key has been purged and the memory + * must be freed as soon as @e rc hits zero. + */ + bool purge; + +}; + + +struct Denomination +{ + + /** + * Kept in a DLL. Sorted by #denomination_action_time(). + */ + struct Denomination *next; + + /** + * Kept in a DLL. Sorted by #denomination_action_time(). + */ + struct Denomination *prev; + + /** + * Head of DLL of actual keys of this denomination. + */ + struct DenominationKey *keys_head; + + /** + * Tail of DLL of actual keys of this denomination. + */ + struct DenominationKey *keys_tail; + + /** + * How long can coins be withdrawn (generated)? Should be small + * enough to limit how many coins will be signed into existence with + * the same key, but large enough to still provide a reasonable + * anonymity set. + */ + struct GNUNET_TIME_Relative duration_withdraw; + + /** + * What is the configuration section of this denomination type? Also used + * for the directory name where the denomination keys are stored. + */ + char *section; + +}; + + +/** + * A semaphore. + */ +struct Semaphore +{ + /** + * Mutex for the semaphore. + */ + pthread_mutex_t mutex; + + /** + * Condition variable for the semaphore. + */ + pthread_cond_t cv; + + /** + * Counter of the semaphore. + */ + unsigned int ctr; +}; + + +/** + * Job in a batch sign request. + */ +struct BatchJob; + +/** + * Handle for a thread that does work in batch signing. + */ +struct Worker +{ + /** + * Kept in a DLL. + */ + struct Worker *prev; + + /** + * Kept in a DLL. + */ + struct Worker *next; + + /** + * Job this worker should do next. + */ + struct BatchJob *job; + + /** + * Semaphore to signal the worker that a job is available. + */ + struct Semaphore sem; + + /** + * Handle for this thread. + */ + pthread_t pt; + + /** + * Set to true if the worker should terminate. + */ + bool do_shutdown; +}; + + +/** + * Job in a batch sign request. + */ +struct BatchJob +{ + + /** + * Thread doing the work. + */ + struct Worker *worker; + + /** + * Semaphore to signal that the job is finished. + */ + struct Semaphore sem; + + /** + * Computation status. + */ + enum TALER_ErrorCode ec; + + /** + * Which type of request is this? + */ + enum { TYPE_SIGN, TYPE_RDERIVE } type; + + /** + * Details depending on @e type. + */ + union + { + + /** + * Details if @e type is TYPE_SIGN. + */ + struct + { + /** + * Request we are working on. + */ + const struct TALER_CRYPTO_CsSignRequestMessage *sr; + + /** + * Result with the signature. + */ + struct GNUNET_CRYPTO_CsBlindSignature cs_answer; + } sign; + + /** + * Details if type is TYPE_RDERIVE. + */ + struct + { + /** + * Request we are answering. + */ + const struct TALER_CRYPTO_CsRDeriveRequest *rdr; + + /** + * Pair of points to return. + */ + struct GNUNET_CRYPTO_CSPublicRPairP rpairp; + + } rderive; + + } details; + +}; + +/** + * Head of DLL of workers ready for more work. + */ +static struct Worker *worker_head; + +/** + * Tail of DLL of workers ready for more work. + */ +static struct Worker *worker_tail; + +/** + * Lock for manipulating the worker DLL. + */ +static pthread_mutex_t worker_lock; + +/** + * Total number of workers that were started. + */ +static unsigned int workers; + +/** + * Semaphore used to grab a worker. + */ +static struct Semaphore worker_sem; + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Time when the key update is executed. + * Either the actual current time, or a pretended time. + */ +static struct GNUNET_TIME_Timestamp now; + +/** + * The time for the key update, as passed by the user + * on the command line. + */ +static struct GNUNET_TIME_Timestamp now_tmp; + +/** + * Where do we store the keys? + */ +static char *keydir; + +/** + * Name of the configuration section prefix to use. Usually either "taler-exchange" or + * "donau". The actual configuration section will then be + * "$SECTION-secmod-cs". + */ +static char *section; + +/** + * How much should coin creation (@e duration_withdraw) duration overlap + * with the next denomination? Basically, the starting time of two + * denominations is always @e duration_withdraw - #overlap_duration apart. + */ +static struct GNUNET_TIME_Relative overlap_duration; + +/** + * How long into the future do we pre-generate keys? + */ +static struct GNUNET_TIME_Relative lookahead_sign; + +/** + * All of our denominations, in a DLL. Sorted? + */ +static struct Denomination *denom_head; + +/** + * All of our denominations, in a DLL. Sorted? + */ +static struct Denomination *denom_tail; + +/** + * Map of hashes of public (CS) keys to `struct DenominationKey *` + * with the respective private keys. + */ +static struct GNUNET_CONTAINER_MultiHashMap *keys; + +/** + * Task run to generate new keys. + */ +static struct GNUNET_SCHEDULER_Task *keygen_task; + +/** + * Lock for the keys queue. + */ +static pthread_mutex_t keys_lock; + +/** + * Current key generation. + */ +static uint64_t key_gen; + +/** + * Number of workers to launch. Note that connections to + * exchanges are NOT workers. + */ +static unsigned int max_workers = 16; + + +/** + * Generate the announcement message for @a dk. + * + * @param[in,out] dk denomination key to generate the announcement for + */ +static void +generate_response (struct DenominationKey *dk) +{ + struct Denomination *denom = dk->denom; + size_t nlen = strlen (denom->section) + 1; + struct TALER_CRYPTO_CsKeyAvailableNotification *an; + void *p; + size_t tlen; + + GNUNET_assert (sizeof(dk->denom_pub) < UINT16_MAX); + GNUNET_assert (nlen < UINT16_MAX); + tlen = nlen + sizeof (*an); + GNUNET_assert (tlen < UINT16_MAX); + an = GNUNET_malloc (tlen); + an->header.size = htons ((uint16_t) tlen); + an->header.type = htons (TALER_HELPER_CS_MT_AVAIL); + an->section_name_len = htons ((uint16_t) nlen); + an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor); + an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw); + an->denom_pub = dk->denom_pub; + TALER_exchange_secmod_cs_sign (&dk->h_cs, + denom->section, + dk->anchor, + denom->duration_withdraw, + &TES_smpriv, + &an->secm_sig); + an->secm_pub = TES_smpub; + p = (void *) &an[1]; + GNUNET_memcpy (p, + denom->section, + nlen); + dk->an = an; +} + + +/** + * Do the actual signing work. + * + * @param h_cs hash of key to sign with + * @param planchet message to sign + * @param for_melt true if for melting + * @param[out] cs_sigp set to the CS signature + * @return #TALER_EC_NONE on success + */ +static enum TALER_ErrorCode +do_sign (const struct TALER_CsPubHashP *h_cs, + const struct GNUNET_CRYPTO_CsBlindedMessage *planchet, + bool for_melt, + struct GNUNET_CRYPTO_CsBlindSignature *cs_sigp) +{ + struct GNUNET_CRYPTO_CsRSecret r[2]; + struct DenominationKey *dk; + + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + dk = GNUNET_CONTAINER_multihashmap_get (keys, + &h_cs->hash); + if (NULL == dk) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, denomination key %s unknown\n", + GNUNET_h2s (&h_cs->hash)); + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } + if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing request failed, denomination key %s is not yet valid\n", + GNUNET_h2s (&h_cs->hash)); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received request to sign over bytes with key %s\n", + GNUNET_h2s (&h_cs->hash)); + GNUNET_assert (dk->rc < UINT_MAX); + dk->rc++; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_CRYPTO_cs_r_derive (&planchet->nonce, + for_melt ? "rm" : "rw", + &dk->denom_priv, + r); + GNUNET_CRYPTO_cs_sign_derive (&dk->denom_priv, + r, + planchet, + cs_sigp); + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + GNUNET_assert (dk->rc > 0); + dk->rc--; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + return TALER_EC_NONE; +} + + +/** + * Generate error response that signing failed. + * + * @param client client to send response to + * @param ec error code to include + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +fail_sign (struct TES_Client *client, + enum TALER_ErrorCode ec) +{ + struct TALER_CRYPTO_SignFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_CS_MT_RES_SIGN_FAILURE), + .ec = htonl (ec) + }; + + return TES_transmit (client->csock, + &sf.header); +} + + +/** + * Generate error response that deriving failed. + * + * @param client client to send response to + * @param ec error code to include + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +fail_derive (struct TES_Client *client, + enum TALER_ErrorCode ec) +{ + struct TALER_CRYPTO_RDeriveFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE), + .ec = htonl (ec) + }; + + return TES_transmit (client->csock, + &sf.header); +} + + +/** + * Generate signature response. + * + * @param client client to send response to + * @param cs_answer signature to send + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +send_signature (struct TES_Client *client, + const struct GNUNET_CRYPTO_CsBlindSignature *cs_answer) +{ + struct TALER_CRYPTO_SignResponse sres; + + sres.header.size = htons (sizeof (sres)); + sres.header.type = htons (TALER_HELPER_CS_MT_RES_SIGNATURE); + sres.b = htonl (cs_answer->b); + sres.cs_answer = cs_answer->s_scalar; + return TES_transmit (client->csock, + &sres.header); +} + + +/** + * Handle @a client request @a sr to create signature. Create the + * signature using the respective key and return the result to + * the client. + * + * @param client the client making the request + * @param sr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_sign_request (struct TES_Client *client, + const struct TALER_CRYPTO_CsSignRequestMessage *sr) +{ + struct GNUNET_CRYPTO_CsBlindSignature cs_answer; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + enum TALER_ErrorCode ec; + enum GNUNET_GenericReturnValue ret; + + ec = do_sign (&sr->h_cs, + &sr->message, + (0 != ntohl (sr->for_melt)), + &cs_answer); + if (TALER_EC_NONE != ec) + { + return fail_sign (client, + ec); + } + ret = send_signature (client, + &cs_answer); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sent CS signature after %s\n", + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_duration (now), + GNUNET_YES)); + return ret; +} + + +/** + * Do the actual deriving work. + * + * @param h_cs key to sign with + * @param nonce nonce to derive from + * @param for_melt true if for melting + * @param[out] rpairp set to the derived values + * @return #TALER_EC_NONE on success + */ +static enum TALER_ErrorCode +do_derive (const struct TALER_CsPubHashP *h_cs, + const struct GNUNET_CRYPTO_CsSessionNonce *nonce, + bool for_melt, + struct GNUNET_CRYPTO_CSPublicRPairP *rpairp) +{ + struct DenominationKey *dk; + struct GNUNET_CRYPTO_CSPrivateRPairP r_priv; + + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + dk = GNUNET_CONTAINER_multihashmap_get (keys, + &h_cs->hash); + if (NULL == dk) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "R Derive request failed, denomination key %s unknown\n", + GNUNET_h2s (&h_cs->hash)); + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; + } + if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "R Derive request failed, denomination key %s is not yet valid\n", + GNUNET_h2s (&h_cs->hash)); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received request to derive R with key %s\n", + GNUNET_h2s (&h_cs->hash)); + GNUNET_assert (dk->rc < UINT_MAX); + dk->rc++; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_CRYPTO_cs_r_derive (nonce, + for_melt ? "rm" : "rw", + &dk->denom_priv, + r_priv.r); + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + GNUNET_assert (dk->rc > 0); + dk->rc--; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[0], + &rpairp->r_pub[0]); + GNUNET_CRYPTO_cs_r_get_public (&r_priv.r[1], + &rpairp->r_pub[1]); + return TALER_EC_NONE; +} + + +/** + * Generate derivation response. + * + * @param client client to send response to + * @param r_pub public point value pair to send + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +send_derivation (struct TES_Client *client, + const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub) +{ + struct TALER_CRYPTO_RDeriveResponse rdr = { + .header.size = htons (sizeof (rdr)), + .header.type = htons (TALER_HELPER_CS_MT_RES_RDERIVE), + .r_pub = *r_pub + }; + + return TES_transmit (client->csock, + &rdr.header); +} + + +/** + * Initialize a semaphore @a sem with a value of @a val. + * + * @param[out] sem semaphore to initialize + * @param val initial value of the semaphore + */ +static void +sem_init (struct Semaphore *sem, + unsigned int val) +{ + GNUNET_assert (0 == + pthread_mutex_init (&sem->mutex, + NULL)); + GNUNET_assert (0 == + pthread_cond_init (&sem->cv, + NULL)); + sem->ctr = val; +} + + +/** + * Decrement semaphore, blocks until this is possible. + * + * @param[in,out] sem semaphore to decrement + */ +static void +sem_down (struct Semaphore *sem) +{ + GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex)); + while (0 == sem->ctr) + { + pthread_cond_wait (&sem->cv, + &sem->mutex); + } + sem->ctr--; + GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex)); +} + + +/** + * Increment semaphore, blocks until this is possible. + * + * @param[in,out] sem semaphore to decrement + */ +static void +sem_up (struct Semaphore *sem) +{ + GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex)); + sem->ctr++; + GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex)); + pthread_cond_signal (&sem->cv); +} + + +/** + * Release resources used by @a sem. + * + * @param[in] sem semaphore to release (except the memory itself) + */ +static void +sem_done (struct Semaphore *sem) +{ + GNUNET_break (0 == pthread_cond_destroy (&sem->cv)); + GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex)); +} + + +/** + * Main logic of a worker thread. Grabs work, does it, + * grabs more work. + * + * @param cls a `struct Worker *` + * @returns cls + */ +static void * +worker (void *cls) +{ + struct Worker *w = cls; + + while (true) + { + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + GNUNET_CONTAINER_DLL_insert (worker_head, + worker_tail, + w); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + sem_up (&worker_sem); + sem_down (&w->sem); + if (w->do_shutdown) + break; + { + struct BatchJob *bj = w->job; + + switch (bj->type) + { + case TYPE_SIGN: + { + const struct TALER_CRYPTO_CsSignRequestMessage *sr + = bj->details.sign.sr; + + bj->ec = do_sign (&sr->h_cs, + &sr->message, + (0 != ntohl (sr->for_melt)), + &bj->details.sign.cs_answer); + break; + } + case TYPE_RDERIVE: + { + const struct TALER_CRYPTO_CsRDeriveRequest *rdr + = bj->details.rderive.rdr; + bj->ec = do_derive (&rdr->h_cs, + &rdr->nonce, + (0 != ntohl (rdr->for_melt)), + &bj->details.rderive.rpairp); + break; + } + } + sem_up (&bj->sem); + w->job = NULL; + } + } + return w; +} + + +/** + * Start batch job @a bj to sign @a sr. + * + * @param sr signature request to answer + * @param[out] bj job data structure + */ +static void +start_sign_job (const struct TALER_CRYPTO_CsSignRequestMessage *sr, + struct BatchJob *bj) +{ + sem_init (&bj->sem, + 0); + bj->type = TYPE_SIGN; + bj->details.sign.sr = sr; + sem_down (&worker_sem); + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + bj->worker = worker_head; + GNUNET_CONTAINER_DLL_remove (worker_head, + worker_tail, + bj->worker); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + bj->worker->job = bj; + sem_up (&bj->worker->sem); +} + + +/** + * Start batch job @a bj to derive @a rdr. + * + * @param rdr derivation request to answer + * @param[out] bj job data structure + */ +static void +start_derive_job (const struct TALER_CRYPTO_CsRDeriveRequest *rdr, + struct BatchJob *bj) +{ + sem_init (&bj->sem, + 0); + bj->type = TYPE_RDERIVE; + bj->details.rderive.rdr = rdr; + sem_down (&worker_sem); + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + bj->worker = worker_head; + GNUNET_CONTAINER_DLL_remove (worker_head, + worker_tail, + bj->worker); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + bj->worker->job = bj; + sem_up (&bj->worker->sem); +} + + +/** + * Finish a job @a bj for a @a client. + * + * @param client who made the request + * @param[in,out] bj job to finish + */ +static void +finish_job (struct TES_Client *client, + struct BatchJob *bj) +{ + sem_down (&bj->sem); + sem_done (&bj->sem); + switch (bj->type) + { + case TYPE_SIGN: + if (TALER_EC_NONE != bj->ec) + { + fail_sign (client, + bj->ec); + return; + } + send_signature (client, + &bj->details.sign.cs_answer); + break; + case TYPE_RDERIVE: + if (TALER_EC_NONE != bj->ec) + { + fail_derive (client, + bj->ec); + return; + } + send_derivation (client, + &bj->details.rderive.rpairp); + break; + } +} + + +/** + * Handle @a client request @a sr to create a batch of signature. Creates the + * signatures using the respective key and return the results to the client. + * + * @param client the client making the request + * @param bsr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_batch_sign_request (struct TES_Client *client, + const struct TALER_CRYPTO_BatchSignRequest *bsr) +{ + uint32_t bs = ntohl (bsr->batch_size); + uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr); + const void *off = (const void *) &bsr[1]; + unsigned int idx = 0; + struct BatchJob jobs[GNUNET_NZL (bs)]; + bool failure = false; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling batch sign request of size %u\n", + (unsigned int) bs); + if (bs > TALER_MAX_FRESH_COINS) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + while ( (bs > 0) && + (size >= sizeof (struct TALER_CRYPTO_CsSignRequestMessage)) ) + { + const struct TALER_CRYPTO_CsSignRequestMessage *sr = off; + uint16_t s = ntohs (sr->header.size); + + if (s > size) + { + failure = true; + bs = idx; + break; + } + start_sign_job (sr, + &jobs[idx++]); + off += s; + size -= s; + } + GNUNET_break_op (0 == size); + bs = GNUNET_MIN (bs, + idx); + for (unsigned int i = 0; i<bs; i++) + finish_job (client, + &jobs[i]); + if (failure) + { + struct TALER_CRYPTO_SignFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE), + .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE) + }; + + GNUNET_break (0); + return TES_transmit (client->csock, + &sf.header); + } + return GNUNET_OK; +} + + +/** + * Handle @a client request @a sr to create a batch of derivations. Creates the + * derivations using the respective key and return the results to the client. + * + * @param client the client making the request + * @param bdr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_batch_derive_request (struct TES_Client *client, + const struct TALER_CRYPTO_BatchDeriveRequest *bdr) +{ + uint32_t bs = ntohl (bdr->batch_size); + uint16_t size = ntohs (bdr->header.size) - sizeof (*bdr); + const void *off = (const void *) &bdr[1]; + unsigned int idx = 0; + struct BatchJob jobs[bs]; + bool failure = false; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling batch derivation request of size %u\n", + (unsigned int) bs); + if (bs > TALER_MAX_FRESH_COINS) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + while ( (bs > 0) && + (size >= sizeof (struct TALER_CRYPTO_CsRDeriveRequest)) ) + { + const struct TALER_CRYPTO_CsRDeriveRequest *rdr = off; + uint16_t s = ntohs (rdr->header.size); + + if ( (s > size) || + (s != sizeof (*rdr)) ) + { + failure = true; + bs = idx; + break; + } + start_derive_job (rdr, + &jobs[idx++]); + off += s; + size -= s; + } + GNUNET_break_op (0 == size); + bs = GNUNET_MIN (bs, + idx); + for (unsigned int i = 0; i<bs; i++) + finish_job (client, + &jobs[i]); + if (failure) + { + GNUNET_break (0); + return fail_derive (client, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE); + } + return GNUNET_OK; +} + + +/** + * Start worker thread for batch processing. + * + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +start_worker (void) +{ + struct Worker *w; + + w = GNUNET_new (struct Worker); + sem_init (&w->sem, + 0); + if (0 != pthread_create (&w->pt, + NULL, + &worker, + w)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "pthread_create"); + GNUNET_free (w); + return GNUNET_SYSERR; + } + workers++; + return GNUNET_OK; +} + + +/** + * Stop all worker threads. + */ +static void +stop_workers (void) +{ + while (workers > 0) + { + struct Worker *w; + void *result; + + sem_down (&worker_sem); + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + w = worker_head; + GNUNET_CONTAINER_DLL_remove (worker_head, + worker_tail, + w); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + w->do_shutdown = true; + sem_up (&w->sem); + pthread_join (w->pt, + &result); + GNUNET_assert (result == w); + sem_done (&w->sem); + GNUNET_free (w); + workers--; + } +} + + +/** + * Initialize key material for denomination key @a dk (also on disk). + * + * @param[in,out] dk denomination key to compute key material for + * @param position where in the DLL will the @a dk go + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +setup_key (struct DenominationKey *dk, + struct DenominationKey *position) +{ + struct Denomination *denom = dk->denom; + struct GNUNET_CRYPTO_CsPrivateKey priv; + struct GNUNET_CRYPTO_CsPublicKey pub; + + GNUNET_CRYPTO_cs_private_key_generate (&priv); + GNUNET_CRYPTO_cs_private_key_get_public (&priv, + &pub); + GNUNET_CRYPTO_hash (&pub, + sizeof (pub), + &dk->h_cs.hash); + GNUNET_asprintf (&dk->filename, + "%s/%s/%llu", + keydir, + denom->section, + (unsigned long long) (dk->anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); + if (GNUNET_OK != + GNUNET_DISK_fn_write (dk->filename, + &priv, + sizeof(priv), + GNUNET_DISK_PERM_USER_READ)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "write", + dk->filename); + return GNUNET_SYSERR; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setup fresh private key %s at %s in `%s' (generation #%llu)\n", + GNUNET_h2s (&dk->h_cs.hash), + GNUNET_TIME_timestamp2s (dk->anchor), + dk->filename, + (unsigned long long) key_gen); + dk->denom_priv = priv; + dk->denom_pub = pub; + dk->key_gen = key_gen; + generate_response (dk); + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put ( + keys, + &dk->h_cs.hash, + dk, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Duplicate private key created! Terminating.\n"); + GNUNET_free (dk->filename); + GNUNET_free (dk->an); + GNUNET_free (dk); + return GNUNET_SYSERR; + } + GNUNET_CONTAINER_DLL_insert_after (denom->keys_head, + denom->keys_tail, + position, + dk); + return GNUNET_OK; +} + + +/** + * The withdraw period of a key @a dk has expired. Purge it. + * + * @param[in] dk expired denomination key to purge + */ +static void +purge_key (struct DenominationKey *dk) +{ + if (dk->purge) + return; + if (0 != unlink (dk->filename)) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + dk->filename); + GNUNET_free (dk->filename); + dk->purge = true; + dk->key_gen = key_gen; +} + + +/** + * A @a client informs us that a key has been revoked. + * Check if the key is still in use, and if so replace (!) + * it with a fresh key. + * + * @param client the client making the request + * @param rr the revocation request + */ +static enum GNUNET_GenericReturnValue +handle_revoke_request (struct TES_Client *client, + const struct TALER_CRYPTO_CsRevokeRequest *rr) +{ + struct DenominationKey *dk; + struct DenominationKey *ndk; + struct Denomination *denom; + + (void) client; + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + dk = GNUNET_CONTAINER_multihashmap_get (keys, + &rr->h_cs.hash); + if (NULL == dk) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Revocation request ignored, denomination key %s unknown\n", + GNUNET_h2s (&rr->h_cs.hash)); + return GNUNET_OK; + } + if (dk->purge) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Revocation request ignored, denomination key %s already revoked\n", + GNUNET_h2s (&rr->h_cs.hash)); + return GNUNET_OK; + } + + key_gen++; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Revoking key %s, bumping generation to %llu\n", + GNUNET_h2s (&rr->h_cs.hash), + (unsigned long long) key_gen); + purge_key (dk); + + /* Setup replacement key */ + denom = dk->denom; + ndk = GNUNET_new (struct DenominationKey); + ndk->denom = denom; + ndk->anchor = dk->anchor; + if (GNUNET_OK != + setup_key (ndk, + dk)) + { + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + global_ret = EXIT_FAILURE; + return GNUNET_SYSERR; + } + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + TES_wake_clients (); + return GNUNET_OK; +} + + +/** + * Handle @a client request @a rdr to create signature. Create the + * signature using the respective key and return the result to + * the client. + * + * @param client the client making the request + * @param rdr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_r_derive_request (struct TES_Client *client, + const struct TALER_CRYPTO_CsRDeriveRequest *rdr) +{ + struct GNUNET_CRYPTO_CSPublicRPairP r_pub; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + enum TALER_ErrorCode ec; + enum GNUNET_GenericReturnValue ret; + + ec = do_derive (&rdr->h_cs, + &rdr->nonce, + (0 != ntohl (rdr->for_melt)), + &r_pub); + if (TALER_EC_NONE != ec) + { + return fail_derive (client, + ec); + } + + ret = send_derivation (client, + &r_pub); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sent CS Derived R after %s\n", + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_duration (now), + GNUNET_YES)); + return ret; +} + + +/** + * Handle @a hdr message received from @a client. + * + * @param client the client that received the message + * @param hdr message that was received + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +cs_work_dispatch (struct TES_Client *client, + const struct GNUNET_MessageHeader *hdr) +{ + uint16_t msize = ntohs (hdr->size); + + switch (ntohs (hdr->type)) + { + case TALER_HELPER_CS_MT_REQ_SIGN: + if (msize < sizeof (struct TALER_CRYPTO_CsSignRequestMessage)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_sign_request ( + client, + (const struct TALER_CRYPTO_CsSignRequestMessage *) hdr); + case TALER_HELPER_CS_MT_REQ_REVOKE: + if (msize != sizeof (struct TALER_CRYPTO_CsRevokeRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_revoke_request ( + client, + (const struct TALER_CRYPTO_CsRevokeRequest *) hdr); + case TALER_HELPER_CS_MT_REQ_BATCH_SIGN: + if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_batch_sign_request ( + client, + (const struct TALER_CRYPTO_BatchSignRequest *) hdr); + case TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE: + if (msize <= sizeof (struct TALER_CRYPTO_BatchDeriveRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_batch_derive_request ( + client, + (const struct TALER_CRYPTO_BatchDeriveRequest *) hdr); + case TALER_HELPER_CS_MT_REQ_RDERIVE: + if (msize != sizeof (struct TALER_CRYPTO_CsRDeriveRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_r_derive_request (client, + (const struct + TALER_CRYPTO_CsRDeriveRequest *) hdr); + default: + GNUNET_break_op (0); + return GNUNET_SYSERR; + } +} + + +/** + * Send our initial key set to @a client together with the + * "sync" terminator. + * + * @param client the client to inform + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +cs_client_init (struct TES_Client *client) +{ + size_t obs = 0; + char *buf; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Initializing new client %p\n", + client); + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + for (struct DenominationKey *dk = denom->keys_head; + NULL != dk; + dk = dk->next) + { + obs += ntohs (dk->an->header.size); + } + } + buf = GNUNET_malloc (obs); + obs = 0; + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + for (struct DenominationKey *dk = denom->keys_head; + NULL != dk; + dk = dk->next) + { + GNUNET_memcpy (&buf[obs], + dk->an, + ntohs (dk->an->header.size)); + obs += ntohs (dk->an->header.size); + } + } + client->key_gen = key_gen; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + if (GNUNET_OK != + TES_transmit_raw (client->csock, + obs, + buf)) + { + GNUNET_free (buf); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Client %p must have disconnected\n", + client); + return GNUNET_SYSERR; + } + GNUNET_free (buf); + { + struct GNUNET_MessageHeader synced = { + .type = htons (TALER_HELPER_CS_SYNCED), + .size = htons (sizeof (synced)) + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sending CS SYNCED message to %p\n", + client); + if (GNUNET_OK != + TES_transmit (client->csock, + &synced)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Notify @a client about all changes to the keys since + * the last generation known to the @a client. + * + * @param client the client to notify + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +cs_update_client_keys (struct TES_Client *client) +{ + size_t obs = 0; + char *buf; + enum GNUNET_GenericReturnValue ret; + + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + for (struct DenominationKey *key = denom->keys_head; + NULL != key; + key = key->next) + { + if (key->key_gen <= client->key_gen) + continue; + if (key->purge) + obs += sizeof (struct TALER_CRYPTO_CsKeyPurgeNotification); + else + obs += ntohs (key->an->header.size); + } + } + if (0 == obs) + { + /* nothing to do */ + client->key_gen = key_gen; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + return GNUNET_OK; + } + buf = GNUNET_malloc (obs); + obs = 0; + for (struct Denomination *denom = denom_head; + NULL != denom; + denom = denom->next) + { + for (struct DenominationKey *key = denom->keys_head; + NULL != key; + key = key->next) + { + if (key->key_gen <= client->key_gen) + continue; + if (key->purge) + { + struct TALER_CRYPTO_CsKeyPurgeNotification pn = { + .header.type = htons (TALER_HELPER_CS_MT_PURGE), + .header.size = htons (sizeof (pn)), + .h_cs = key->h_cs + }; + + GNUNET_memcpy (&buf[obs], + &pn, + sizeof (pn)); + GNUNET_assert (obs + sizeof (pn) + > obs); + obs += sizeof (pn); + } + else + { + GNUNET_memcpy (&buf[obs], + key->an, + ntohs (key->an->header.size)); + GNUNET_assert (obs + ntohs (key->an->header.size) + > obs); + obs += ntohs (key->an->header.size); + } + } + } + client->key_gen = key_gen; + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + ret = TES_transmit_raw (client->csock, + obs, + buf); + GNUNET_free (buf); + return ret; +} + + +/** + * Create a new denomination key (we do not have enough). + * + * @param denom denomination key to create + * @param now current time to use (to get many keys to use the exact same time) + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +create_key (struct Denomination *denom, + struct GNUNET_TIME_Timestamp now) +{ + struct DenominationKey *dk; + struct GNUNET_TIME_Timestamp anchor; + + anchor = now; + if (NULL != denom->keys_tail) + { + struct GNUNET_TIME_Absolute abs; + + abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, + GNUNET_TIME_relative_subtract ( + denom->duration_withdraw, + overlap_duration)); + if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs)) + anchor = GNUNET_TIME_absolute_to_timestamp (abs); + } + dk = GNUNET_new (struct DenominationKey); + dk->denom = denom; + dk->anchor = anchor; + if (GNUNET_OK != + setup_key (dk, + denom->keys_tail)) + { + GNUNET_break (0); + GNUNET_free (dk); + GNUNET_SCHEDULER_shutdown (); + global_ret = EXIT_FAILURE; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * At what time does this denomination require its next action? + * Basically, the minimum of the withdraw expiration time of the + * oldest denomination key, and the withdraw expiration time of + * the newest denomination key minus the #lookahead_sign time. + * + * @param denom denomination to compute action time for + */ +static struct GNUNET_TIME_Absolute +denomination_action_time (const struct Denomination *denom) +{ + struct DenominationKey *head = denom->keys_head; + struct DenominationKey *tail = denom->keys_tail; + struct GNUNET_TIME_Absolute tt; + + if (NULL == head) + return GNUNET_TIME_UNIT_ZERO_ABS; + tt = GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (tail->anchor.abs_time, + denom->duration_withdraw), + lookahead_sign), + overlap_duration); + if (head->rc > 0) + return tt; /* head expiration does not count due to rc > 0 */ + return GNUNET_TIME_absolute_min ( + GNUNET_TIME_absolute_add (head->anchor.abs_time, + denom->duration_withdraw), + tt); +} + + +/** + * Create new keys and expire ancient keys of the given denomination @a denom. + * Removes the @a denom from the #denom_head DLL and re-insert its at the + * correct location sorted by next maintenance activity. + * + * @param[in,out] denom denomination to update material for + * @param now current time to use (to get many keys to use the exact same time) + * @param[in,out] wake set to true if we should wake the clients + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +update_keys (struct Denomination *denom, + struct GNUNET_TIME_Timestamp now, + bool *wake) +{ + /* create new denomination keys */ + if (NULL != denom->keys_tail) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Updating keys of denomination `%s', last key %s valid for another %s\n", + denom->section, + GNUNET_h2s (&denom->keys_tail->h_cs.hash), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add ( + denom->keys_tail->anchor.abs_time, + denom->duration_withdraw), + overlap_duration)), + GNUNET_YES)); + while ( (NULL == denom->keys_tail) || + GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, + denom->duration_withdraw), + lookahead_sign), + overlap_duration)) ) + { + if (! *wake) + { + key_gen++; + *wake = true; + } + if (GNUNET_OK != + create_key (denom, + now)) + { + GNUNET_break (0); + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return GNUNET_SYSERR; + } + } + /* remove expired denomination keys */ + while ( (NULL != denom->keys_head) && + GNUNET_TIME_absolute_is_past + (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time, + denom->duration_withdraw)) ) + { + struct DenominationKey *key = denom->keys_head; + struct DenominationKey *nxt = key->next; + + if (0 != key->rc) + break; /* later */ + GNUNET_CONTAINER_DLL_remove (denom->keys_head, + denom->keys_tail, + key); + GNUNET_assert (GNUNET_OK == + GNUNET_CONTAINER_multihashmap_remove ( + keys, + &key->h_cs.hash, + key)); + if ( (! key->purge) && + (0 != unlink (key->filename)) ) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "unlink", + key->filename); + GNUNET_free (key->filename); + GNUNET_free (key->an); + GNUNET_free (key); + key = nxt; + } + + /* Update position of 'denom' in #denom_head DLL: sort by action time */ + { + struct Denomination *before; + struct GNUNET_TIME_Absolute at; + + at = denomination_action_time (denom); + GNUNET_CONTAINER_DLL_remove (denom_head, + denom_tail, + denom); + before = NULL; + for (struct Denomination *pos = denom_head; + NULL != pos; + pos = pos->next) + { + if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at)) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (denom_head, + denom_tail, + before, + denom); + } + return GNUNET_OK; +} + + +/** + * Task run periodically to expire keys and/or generate fresh ones. + * + * @param cls NULL + */ +static void +update_denominations (void *cls) +{ + struct Denomination *denom; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp t; + bool wake = false; + + (void) cls; + keygen_task = NULL; + now = GNUNET_TIME_absolute_get (); + t = GNUNET_TIME_absolute_to_timestamp (now); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations ...\n"); + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + do { + denom = denom_head; + if (GNUNET_OK != + update_keys (denom, + t, + &wake)) + return; + } while (denom != denom_head); + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Updating denominations finished ...\n"); + if (wake) + TES_wake_clients (); + keygen_task = GNUNET_SCHEDULER_add_at (denomination_action_time (denom), + &update_denominations, + NULL); +} + + +/** + * Parse private key of denomination @a denom in @a buf. + * + * @param[out] denom denomination of the key + * @param filename name of the file we are parsing, for logging + * @param priv key material + */ +static void +parse_key (struct Denomination *denom, + const char *filename, + const struct GNUNET_CRYPTO_CsPrivateKey *priv) +{ + char *anchor_s; + char dummy; + unsigned long long anchor_ll; + struct GNUNET_TIME_Timestamp anchor; + + anchor_s = strrchr (filename, + '/'); + if (NULL == anchor_s) + { + /* File in a directory without '/' in the name, this makes no sense. */ + GNUNET_break (0); + return; + } + anchor_s++; + if (1 != sscanf (anchor_s, + "%llu%c", + &anchor_ll, + &dummy)) + { + /* Filenames in KEYDIR must ONLY be the anchor time in seconds! */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return; + } + anchor.abs_time.abs_value_us + = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + { + /* Integer overflow. Bad, invalid filename. */ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Filename `%s' invalid for key file, skipping\n", + filename); + return; + } + { + struct DenominationKey *dk; + struct DenominationKey *before; + + dk = GNUNET_new (struct DenominationKey); + dk->denom_priv = *priv; + dk->denom = denom; + dk->anchor = anchor; + dk->filename = GNUNET_strdup (filename); + GNUNET_CRYPTO_cs_private_key_get_public (priv, + &dk->denom_pub); + GNUNET_CRYPTO_hash (&dk->denom_pub, + sizeof (dk->denom_pub), + &dk->h_cs.hash); + generate_response (dk); + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put ( + keys, + &dk->h_cs.hash, + dk, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Duplicate private key %s detected in file `%s'. Skipping.\n", + GNUNET_h2s (&dk->h_cs.hash), + filename); + GNUNET_free (dk->an); + GNUNET_free (dk); + return; + } + before = NULL; + for (struct DenominationKey *pos = denom->keys_head; + NULL != pos; + pos = pos->next) + { + if (GNUNET_TIME_timestamp_cmp (pos->anchor, + >, + anchor)) + break; + before = pos; + } + GNUNET_CONTAINER_DLL_insert_after (denom->keys_head, + denom->keys_tail, + before, + dk); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Imported key %s from `%s'\n", + GNUNET_h2s (&dk->h_cs.hash), + filename); + } +} + + +/** + * Import a private key from @a filename for the denomination + * given in @a cls. + * + * @param[in,out] cls a `struct Denomiantion` + * @param filename name of a file in the directory + * @return #GNUNET_OK (always, continue to iterate) + */ +static enum GNUNET_GenericReturnValue +import_key (void *cls, + const char *filename) +{ + struct Denomination *denom = cls; + struct GNUNET_DISK_FileHandle *fh; + struct GNUNET_DISK_MapHandle *map; + void *ptr; + int fd; + struct stat sbuf; + + { + struct stat lsbuf; + + if (0 != lstat (filename, + &lsbuf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "lstat", + filename); + return GNUNET_OK; + } + if (! S_ISREG (lsbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + return GNUNET_OK; + } + } + + fd = open (filename, + O_RDONLY | O_CLOEXEC); + if (-1 == fd) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + return GNUNET_OK; + } + if (0 != fstat (fd, + &sbuf)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "stat", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (! S_ISREG (sbuf.st_mode)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' is not a regular file, which is not allowed for private keys!\n", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO))) + { + /* permission are NOT tight, try to patch them up! */ + if (0 != + fchmod (fd, + S_IRUSR)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "fchmod", + filename); + /* refuse to use key if file has wrong permissions */ + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + } + fh = GNUNET_DISK_get_handle_from_int_fd (fd); + if (NULL == fh) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "open", + filename); + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + if (sbuf.st_size != sizeof(struct GNUNET_CRYPTO_CsPrivateKey)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "File `%s' too big to be a private key\n", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + ptr = GNUNET_DISK_file_map (fh, + &map, + GNUNET_DISK_MAP_TYPE_READ, + (size_t) sbuf.st_size); + if (NULL == ptr) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "mmap", + filename); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; + } + parse_key (denom, + filename, + (const struct GNUNET_CRYPTO_CsPrivateKey *) ptr); + GNUNET_DISK_file_unmap (map); + GNUNET_DISK_file_close (fh); + return GNUNET_OK; +} + + +/** + * Parse configuration for denomination type parameters. Also determines + * our anchor by looking at the existing denominations of the same type. + * + * @param cfg configuration to use + * @param ct section in the configuration file giving the denomination type parameters + * @param[out] denom set to the denomination parameters from the configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid + */ +static enum GNUNET_GenericReturnValue +parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *ct, + struct Denomination *denom) +{ + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-cs", + section); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + ct, + "DURATION_WITHDRAW", + &denom->duration_withdraw)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "DURATION_WITHDRAW"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } + if (GNUNET_TIME_relative_cmp (overlap_duration, + >=, + denom->duration_withdraw)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + secname, + "OVERLAP_DURATION", + "Value given must be smaller than value for DURATION_WITHDRAW!"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } + GNUNET_free (secname); + denom->section = GNUNET_strdup (ct); + return GNUNET_OK; +} + + +/** + * Closure for #load_denominations. + */ +struct LoadContext +{ + + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Current time to use. + */ + struct GNUNET_TIME_Timestamp t; + + /** + * Status, to be set to #GNUNET_SYSERR on failure + */ + enum GNUNET_GenericReturnValue ret; +}; + + +/** + * Generate new denomination signing keys for the denomination type of the given @a + * denomination_alias. + * + * @param cls a `struct LoadContext`, with 'ret' to be set to #GNUNET_SYSERR on failure + * @param denomination_alias name of the denomination's section in the configuration + */ +static void +load_denominations (void *cls, + const char *denomination_alias) +{ + struct LoadContext *ctx = cls; + struct Denomination *denom; + bool wake = true; + char *cipher; + + if ( (0 != strncasecmp (denomination_alias, + "coin_", + strlen ("coin_"))) && + (0 != strncasecmp (denomination_alias, + "coin-", + strlen ("coin-"))) ) + return; /* not a denomination type definition */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ctx->cfg, + denomination_alias, + "CIPHER", + &cipher)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + denomination_alias, + "CIPHER"); + return; + } + if (0 != strcmp (cipher, "CS")) + { + GNUNET_free (cipher); + return; /* Ignore denominations of other types than CS*/ + } + GNUNET_free (cipher); + + denom = GNUNET_new (struct Denomination); + if (GNUNET_OK != + parse_denomination_cfg (ctx->cfg, + denomination_alias, + denom)) + { + ctx->ret = GNUNET_SYSERR; + GNUNET_free (denom); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading keys for denomination %s\n", + denom->section); + { + char *dname; + + GNUNET_asprintf (&dname, + "%s/%s", + keydir, + denom->section); + GNUNET_break (GNUNET_OK == + GNUNET_DISK_directory_create (dname)); + GNUNET_DISK_directory_scan (dname, + &import_key, + denom); + GNUNET_free (dname); + } + GNUNET_CONTAINER_DLL_insert (denom_head, + denom_tail, + denom); + update_keys (denom, + ctx->t, + &wake); +} + + +/** + * Load the various duration values from @a cfg + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-cs", + section); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + secname, + "OVERLAP_DURATION", + &overlap_duration)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "OVERLAP_DURATION"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (cfg, + secname, + "LOOKAHEAD_SIGN", + &lookahead_sign)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "LOOKAHEAD_SIGN"); + GNUNET_free (secname); + return GNUNET_SYSERR; + } + GNUNET_free (secname); + return GNUNET_OK; +} + + +/** + * Function run on shutdown. Stops the various jobs (nicely). + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + TES_listen_stop (); + if (NULL != keygen_task) + { + GNUNET_SCHEDULER_cancel (keygen_task); + keygen_task = NULL; + } + stop_workers (); + sem_done (&worker_sem); +} + + +/** + * Main function that will be run under the GNUnet scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + static struct TES_Callbacks cb = { + .dispatch = &cs_work_dispatch, + .updater = &cs_update_client_keys, + .init = &cs_client_init + }; + char *secname; + + (void) cls; + (void) args; + (void) cfgfile; + if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp)) + { + /* The user gave "--now", use it! */ + now = now_tmp; + } + else + { + /* get current time again, we may be timetraveling! */ + now = GNUNET_TIME_timestamp_get (); + } + GNUNET_asprintf (&secname, + "%s-secmod-cs", + section); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + secname, + "KEY_DIR", + &keydir)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + secname, + "KEY_DIR"); + GNUNET_free (secname); + global_ret = EXIT_NOTCONFIGURED; + return; + } + GNUNET_free (secname); + if (GNUNET_OK != + load_durations (cfg)) + { + global_ret = EXIT_NOTCONFIGURED; + return; + } + { + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-cs", + section); + global_ret = TES_listen_start (cfg, + secname, + &cb); + GNUNET_free (secname); + } + if (0 != global_ret) + return; + sem_init (&worker_sem, + 0); + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + if (0 == max_workers) + { + long lret; + + lret = sysconf (_SC_NPROCESSORS_CONF); + if (lret <= 0) + lret = 1; + max_workers = (unsigned int) lret; + } + for (unsigned int i = 0; i<max_workers; i++) + if (GNUNET_OK != + start_worker ()) + { + GNUNET_SCHEDULER_shutdown (); + return; + } + /* Load denominations */ + keys = GNUNET_CONTAINER_multihashmap_create (65536, + GNUNET_YES); + { + struct LoadContext lc = { + .cfg = cfg, + .ret = GNUNET_OK, + .t = now + }; + + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); + GNUNET_CONFIGURATION_iterate_sections (cfg, + &load_denominations, + &lc); + GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); + if (GNUNET_OK != lc.ret) + { + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + return; + } + } + if (NULL == denom_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No CS denominations configured\n"); + TES_wake_clients (); + return; + } + /* start job to keep keys up-to-date; MUST be run before the #listen_task, + hence with priority. */ + keygen_task = GNUNET_SCHEDULER_add_with_priority ( + GNUNET_SCHEDULER_PRIORITY_URGENT, + &update_denominations, + NULL); +} + + +/** + * The entry point. + * + * @param argc number of arguments in @a argv + * @param argv command-line arguments + * @return 0 on normal termination + */ +int +main (int argc, + char **argv) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('s', + "section", + "SECTION", + "name of the configuration section prefix to use, default is 'taler'", + §ion), + GNUNET_GETOPT_option_timetravel ('T', + "timetravel"), + GNUNET_GETOPT_option_timestamp ('t', + "time", + "TIMESTAMP", + "pretend it is a different time for the update", + &now_tmp), + GNUNET_GETOPT_option_uint ('w', + "workers", + "COUNT", + "use COUNT workers for parallel processing of batch requests", + &max_workers), + GNUNET_GETOPT_OPTION_END + }; + enum GNUNET_GenericReturnValue ret; + + /* Restrict permissions for the key files that we create. */ + (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH); + section = GNUNET_strdup ("taler-exchange"); + /* force linker to link against libtalerutil; if we do + not do this, the linker may "optimize" libtalerutil + away and skip #TALER_OS_init(), which we do need */ + TALER_OS_init (); + now_tmp = now = GNUNET_TIME_timestamp_get (); + ret = GNUNET_PROGRAM_run (argc, argv, + "taler-exchange-secmod-cs", + "Handle private CS key operations for a Taler exchange", + options, + &run, + NULL); + if (GNUNET_NO == ret) + return EXIT_SUCCESS; + if (GNUNET_SYSERR == ret) + return EXIT_INVALIDARGUMENT; + return global_ret; +} diff --git a/src/util/taler-exchange-secmod-cs.conf b/src/util/taler-exchange-secmod-cs.conf new file mode 100644 index 000000000..fa3cdba40 --- /dev/null +++ b/src/util/taler-exchange-secmod-cs.conf @@ -0,0 +1,23 @@ +[taler-exchange-secmod-cs] + +# How long should generated coins overlap in their validity +# periods. Should be long enough to avoid problems with +# wallets picking one key and then due to network latency +# another key being valid. The DURATION_WITHDRAW period +# must be longer than this value. +OVERLAP_DURATION = 5 m + +# Where do we store the generated private keys. +KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-cs/keys + +# Where does the helper listen for requests? +UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-cs/server.sock + +# Directory for clients. +CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-cs/clients + +# Where should the security module store its own private key? +SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-cs/secmod-private-key + +# For how long into the future do we pre-generate keys? +LOOKAHEAD_SIGN = 1 year diff --git a/src/util/taler-exchange-secmod-cs.h b/src/util/taler-exchange-secmod-cs.h new file mode 100644 index 000000000..0321335da --- /dev/null +++ b/src/util/taler-exchange-secmod-cs.h @@ -0,0 +1,319 @@ +/* + This file is part of TALER + Copyright (C) 2020-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/taler-exchange-secmod-cs.h + * @brief IPC messages for the CS crypto helper. + * @author Christian Grothoff + * @author Gian Demarmels + * @author Lucien Heuzeveldt + */ +#ifndef TALER_EXCHANGE_SECMOD_CS_H +#define TALER_EXCHANGE_SECMOD_CS_H + +#define TALER_HELPER_CS_MT_PURGE 1 +#define TALER_HELPER_CS_MT_AVAIL 2 + +#define TALER_HELPER_CS_MT_REQ_INIT 3 +#define TALER_HELPER_CS_MT_REQ_BATCH_SIGN 4 +#define TALER_HELPER_CS_MT_REQ_SIGN 5 +#define TALER_HELPER_CS_MT_REQ_REVOKE 6 +#define TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE 7 +#define TALER_HELPER_CS_MT_REQ_RDERIVE 8 + +#define TALER_HELPER_CS_MT_RES_SIGNATURE 9 +#define TALER_HELPER_CS_MT_RES_SIGN_FAILURE 10 +#define TALER_HELPER_CS_MT_RES_BATCH_SIGN_FAILURE 11 +#define TALER_HELPER_CS_MT_RES_RDERIVE 12 +#define TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE 13 +#define TALER_HELPER_CS_MT_RES_BATCH_RDERIVE_FAILURE 14 + +#define TALER_HELPER_CS_SYNCED 15 + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * Message sent if a key is available. + */ +struct TALER_CRYPTO_CsKeyAvailableNotification +{ + /** + * Type is #TALER_HELPER_CS_MT_AVAIL + */ + struct GNUNET_MessageHeader header; + + /** + * Number of bytes of the section name. + */ + uint32_t section_name_len; + + /** + * When does the key become available? + */ + struct GNUNET_TIME_TimestampNBO anchor_time; + + /** + * How long is the key available after @e anchor_time? + */ + struct GNUNET_TIME_RelativeNBO duration_withdraw; + + /** + * Public key used to generate the @e sicm_sig. + */ + struct TALER_SecurityModulePublicKeyP secm_pub; + + /** + * Signature affirming the announcement, of + * purpose #TALER_SIGNATURE_SM_CS_DENOMINATION_KEY. + */ + struct TALER_SecurityModuleSignatureP secm_sig; + + /** + * Denomination Public key + */ + struct GNUNET_CRYPTO_CsPublicKey denom_pub; + + /* followed by @e section_name bytes of the configuration section name + of the denomination of this key */ + +}; + + +/** + * Message sent if a key was purged. + */ +struct TALER_CRYPTO_CsKeyPurgeNotification +{ + /** + * Type is #TALER_HELPER_CS_MT_PURGE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * Hash of the public key of the purged CS key. + */ + struct TALER_CsPubHashP h_cs; + +}; + + +/** + * Message sent if a signature is requested. + */ +struct TALER_CRYPTO_CsSignRequestMessage +{ + /** + * Type is #TALER_HELPER_CS_MT_REQ_SIGN. + */ + struct GNUNET_MessageHeader header; + + /** + * 0 for withdraw, 1 for melt, in NBO. + */ + uint32_t for_melt; + + /** + * Hash of the public key of the CS key to use for the signature. + */ + struct TALER_CsPubHashP h_cs; + + /** + * Message to sign. + */ + struct GNUNET_CRYPTO_CsBlindedMessage message; + +}; + + +/** + * Message sent if a batch of signatures is requested. + */ +struct TALER_CRYPTO_BatchSignRequest +{ + /** + * Type is #TALER_HELPER_CS_MT_REQ_BATCH_SIGN. + */ + struct GNUNET_MessageHeader header; + + /** + * Number of signatures to create, in NBO. + */ + uint32_t batch_size; + + /* + * Followed by @e batch_size batch sign requests. + */ + +}; + + +/** + * Message sent if a signature is requested. + */ +struct TALER_CRYPTO_CsRDeriveRequest +{ + /** + * Type is #TALER_HELPER_CS_MT_REQ_RDERIVE. + */ + struct GNUNET_MessageHeader header; + + /** + * 0 for withdraw, 1 for melt, in NBO. + */ + uint32_t for_melt; + + /** + * Hash of the public key of the CS key to use for the derivation. + */ + struct TALER_CsPubHashP h_cs; + + /** + * Withdraw nonce to derive R from + */ + struct GNUNET_CRYPTO_CsSessionNonce nonce; +}; + + +/** + * Message sent if a batch of derivations is requested. + */ +struct TALER_CRYPTO_BatchDeriveRequest +{ + /** + * Type is #TALER_HELPER_CS_MT_REQ_BATCH_RDERIVE. + */ + struct GNUNET_MessageHeader header; + + /** + * Number of derivations to create, in NBO. + */ + uint32_t batch_size; + + /* + * Followed by @e batch_size derive requests. + */ + +}; + + +/** + * Message sent if a key was revoked. + */ +struct TALER_CRYPTO_CsRevokeRequest +{ + /** + * Type is #TALER_HELPER_CS_MT_REQ_REVOKE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * Hash of the public key of the revoked CS key. + */ + struct TALER_CsPubHashP h_cs; + +}; + + +/** + * Message sent if a signature was successfully computed. + */ +struct TALER_CRYPTO_SignResponse +{ + /** + * Type is #TALER_HELPER_CS_MT_RES_SIGNATURE. + */ + struct GNUNET_MessageHeader header; + + /** + * The chosen 'b' (0 or 1). + */ + uint32_t b; + + /** + * Contains the blindided s. + */ + struct GNUNET_CRYPTO_CsBlindS cs_answer; +}; + +/** + * Message sent if a R is successfully derived + */ +struct TALER_CRYPTO_RDeriveResponse +{ + /** + * Type is #TALER_HELPER_CS_MT_RES_RDERIVE. + */ + struct GNUNET_MessageHeader header; + + /** + * For now, always zero. + */ + uint32_t reserved; + + /** + * Pair of derived R values + */ + struct GNUNET_CRYPTO_CSPublicRPairP r_pub; +}; + + +/** + * Message sent if signing failed. + */ +struct TALER_CRYPTO_SignFailure +{ + /** + * Type is #TALER_HELPER_CS_MT_RES_SIGN_FAILURE. + */ + struct GNUNET_MessageHeader header; + + /** + * If available, Taler error code. In NBO. + */ + uint32_t ec; + +}; + +/** + * Message sent if derivation failed. + */ +struct TALER_CRYPTO_RDeriveFailure +{ + /** + * Type is #TALER_HELPER_CS_MT_RES_RDERIVE_FAILURE. + */ + struct GNUNET_MessageHeader header; + + /** + * If available, Taler error code. In NBO. + */ + uint32_t ec; + +}; +GNUNET_NETWORK_STRUCT_END + + +#endif diff --git a/src/util/taler-exchange-secmod-eddsa.c b/src/util/taler-exchange-secmod-eddsa.c index 3fe9b284e..0b95447f7 100644 --- a/src/util/taler-exchange-secmod-eddsa.c +++ b/src/util/taler-exchange-secmod-eddsa.c @@ -37,7 +37,6 @@ #include "taler-exchange-secmod-eddsa.h" #include <gcrypt.h> #include <pthread.h> -#include <sys/eventfd.h> #include "taler_error_codes.h" #include "taler_signatures.h" #include "secmod_common.h" @@ -78,7 +77,7 @@ struct Key /** * Time at which this key is supposed to become valid. */ - struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Timestamp anchor; /** * Generation when this key was created or revoked. @@ -124,13 +123,13 @@ static int global_ret; * Time when the key update is executed. * Either the actual current time, or a pretended time. */ -static struct GNUNET_TIME_Absolute now; +static struct GNUNET_TIME_Timestamp now; /** * The time for the key update, as passed by the user * on the command line. */ -static struct GNUNET_TIME_Absolute now_tmp; +static struct GNUNET_TIME_Timestamp now_tmp; /** * Where do we store the keys? @@ -138,6 +137,13 @@ static struct GNUNET_TIME_Absolute now_tmp; static char *keydir; /** + * Name of the configuration section prefix to use. Usually either "taler-exchange" or + * "donau". The actual configuration section will then be + * "$SECTION-secmod-eddsa". + */ +static char *section; + +/** * How much should coin creation duration overlap * with the next key? Basically, the starting time of two * keys is always #duration - #overlap_duration apart. @@ -179,7 +185,7 @@ notify_client_key_add (struct TES_Client *client, struct TALER_CRYPTO_EddsaKeyAvailableNotification an = { .header.size = htons (sizeof (an)), .header.type = htons (TALER_HELPER_EDDSA_MT_AVAIL), - .anchor_time = GNUNET_TIME_absolute_hton (key->anchor), + .anchor_time = GNUNET_TIME_timestamp_hton (key->anchor), .duration = GNUNET_TIME_relative_hton (duration), .exchange_pub = key->exchange_pub, .secm_pub = TES_smpub @@ -274,7 +280,7 @@ handle_sign_request (struct TES_Client *client, key = keys_head; while ( (NULL != key) && (GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_add (key->anchor, + GNUNET_TIME_absolute_add (key->anchor.abs_time, duration))) ) { struct Key *nxt = key->next; @@ -284,9 +290,9 @@ handle_sign_request (struct TES_Client *client, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Deleting past key %s (expired %s ago)\n", TALER_B2S (&nxt->exchange_pub), - GNUNET_STRINGS_relative_time_to_string ( + GNUNET_TIME_relative2s ( GNUNET_TIME_absolute_get_duration ( - GNUNET_TIME_absolute_add (key->anchor, + GNUNET_TIME_absolute_add (key->anchor.abs_time, duration)), GNUNET_YES)); GNUNET_CONTAINER_DLL_remove (keys_head, @@ -364,7 +370,7 @@ setup_key (struct Key *key, GNUNET_asprintf (&key->filename, "%s/%llu", keydir, - (unsigned long long) (key->anchor.abs_value_us + (unsigned long long) (key->anchor.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); if (GNUNET_OK != GNUNET_DISK_fn_write (key->filename, @@ -585,11 +591,11 @@ eddsa_client_init (struct TES_Client *client) static enum GNUNET_GenericReturnValue eddsa_update_client_keys (struct TES_Client *client) { + GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Updating client %p to generation %llu\n", client, (unsigned long long) key_gen); - GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); for (struct Key *key = keys_head; NULL != key; key = key->next) @@ -638,23 +644,21 @@ static enum GNUNET_GenericReturnValue create_key (void) { struct Key *key; - struct GNUNET_TIME_Absolute anchor; - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp anchor; - now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); - if (NULL == keys_tail) - { - anchor = now; - } - else + anchor = GNUNET_TIME_timestamp_get (); + if (NULL != keys_tail) { - anchor = GNUNET_TIME_absolute_add (keys_tail->anchor, - GNUNET_TIME_relative_subtract ( - duration, - overlap_duration)); - if (now.abs_value_us > anchor.abs_value_us) - anchor = now; + struct GNUNET_TIME_Absolute abs; + + abs = GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, + GNUNET_TIME_relative_subtract ( + duration, + overlap_duration)); + if (GNUNET_TIME_absolute_cmp (anchor.abs_time, + <, + abs)) + anchor = GNUNET_TIME_absolute_to_timestamp (abs); } key = GNUNET_new (struct Key); key->anchor = anchor; @@ -689,11 +693,11 @@ key_action_time (void) if (NULL == nxt) return GNUNET_TIME_UNIT_ZERO_ABS; return GNUNET_TIME_absolute_min ( - GNUNET_TIME_absolute_add (nxt->anchor, + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, duration), GNUNET_TIME_absolute_subtract ( GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (keys_tail->anchor, + GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, duration), lookahead_sign), overlap_duration)); @@ -719,7 +723,7 @@ update_keys (void *cls) GNUNET_TIME_absolute_is_past ( GNUNET_TIME_absolute_subtract ( GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (keys_tail->anchor, + GNUNET_TIME_absolute_add (keys_tail->anchor.abs_time, duration), lookahead_sign), overlap_duration)) ) @@ -743,7 +747,7 @@ update_keys (void *cls) /* purge expired keys */ while ( (NULL != nxt) && GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_add (nxt->anchor, + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, duration))) { if (! wake) @@ -754,9 +758,9 @@ update_keys (void *cls) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Purging past key %s (expired %s ago)\n", TALER_B2S (&nxt->exchange_pub), - GNUNET_STRINGS_relative_time_to_string ( + GNUNET_TIME_relative2s ( GNUNET_TIME_absolute_get_duration ( - GNUNET_TIME_absolute_add (nxt->anchor, + GNUNET_TIME_absolute_add (nxt->anchor.abs_time, duration)), GNUNET_YES)); purge_key (nxt); @@ -788,7 +792,7 @@ parse_key (const char *filename, char *anchor_s; char dummy; unsigned long long anchor_ll; - struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Timestamp anchor; anchor_s = strrchr (filename, '/'); @@ -810,8 +814,10 @@ parse_key (const char *filename, filename); return GNUNET_SYSERR; } - anchor.abs_value_us = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; - if (anchor_ll != anchor.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + anchor.abs_time.abs_value_us = anchor_ll + * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) { /* Integer overflow. Bad, invalid filename. */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -827,9 +833,9 @@ parse_key (const char *filename, filename); return GNUNET_SYSERR; } - memcpy (&priv, - buf, - buf_size); + GNUNET_memcpy (&priv, + buf, + buf_size); { struct GNUNET_CRYPTO_EddsaPublicKey pub; @@ -850,7 +856,7 @@ parse_key (const char *filename, NULL != pos; pos = pos->next) { - if (pos->anchor.abs_value_us > anchor.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (pos->anchor, >, anchor)) break; before = pos; } @@ -905,7 +911,7 @@ import_key (void *cls, } fd = open (filename, - O_CLOEXEC); + O_RDONLY | O_CLOEXEC); if (-1 == fd) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, @@ -919,6 +925,7 @@ import_key (void *cls, GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", filename); + GNUNET_break (0 == close (fd)); return GNUNET_OK; } if (! S_ISREG (sbuf.st_mode)) @@ -926,6 +933,7 @@ import_key (void *cls, GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "File `%s' is not a regular file, which is not allowed for private keys!\n", filename); + GNUNET_break (0 == close (fd)); return GNUNET_OK; } if (0 != (sbuf.st_mode & (S_IWUSR | S_IRWXG | S_IRWXO))) @@ -990,42 +998,48 @@ import_key (void *cls, static enum GNUNET_GenericReturnValue load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg) { + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, - "taler-exchange-secmod-eddsa", + secname, "OVERLAP_DURATION", &overlap_duration)) { + GNUNET_free (secname); GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "OVERLAP_DURATION"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, - "taler-exchange-secmod-eddsa", + secname, "DURATION", &duration)) { + GNUNET_free (secname); GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "DURATION"); return GNUNET_SYSERR; } - GNUNET_TIME_round_rel (&overlap_duration); - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, - "taler-exchange-secmod-eddsa", + secname, "LOOKAHEAD_SIGN", &lookahead_sign)) { + GNUNET_free (secname); GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "LOOKAHEAD_SIGN"); return GNUNET_SYSERR; } - GNUNET_TIME_round_rel (&lookahead_sign); + GNUNET_free (secname); return GNUNET_OK; } @@ -1067,11 +1081,12 @@ run (void *cls, .updater = eddsa_update_client_keys, .init = eddsa_client_init }; + char *secname; (void) cls; (void) args; (void) cfgfile; - if (now.abs_value_us != now_tmp.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp)) { /* The user gave "--now", use it! */ now = now_tmp; @@ -1079,9 +1094,11 @@ run (void *cls, else { /* get current time again, we may be timetraveling! */ - now = GNUNET_TIME_absolute_get (); + now = GNUNET_TIME_timestamp_get (); } - GNUNET_TIME_round_abs (&now); + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + section); if (GNUNET_OK != load_durations (cfg)) { @@ -1090,21 +1107,31 @@ run (void *cls, } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-exchange-secmod-eddsa", + secname, "KEY_DIR", &keydir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-eddsa", + secname, "KEY_DIR"); + GNUNET_free (secname); global_ret = EXIT_NOTCONFIGURED; return; } + GNUNET_free (secname); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); - global_ret = TES_listen_start (cfg, - "taler-exchange-secmod-eddsa", - &cb); + { + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-eddsa", + section); + global_ret = TES_listen_start (cfg, + secname, + &cb); + GNUNET_free (secname); + } if (0 != global_ret) return; /* Load keys */ @@ -1114,12 +1141,13 @@ run (void *cls, &import_key, NULL); if ( (NULL != keys_head) && - (GNUNET_TIME_absolute_is_future (keys_head->anchor)) ) + (GNUNET_TIME_absolute_is_future (keys_head->anchor.abs_time)) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Existing anchor is in %s the future. Refusing to start\n", - GNUNET_STRINGS_relative_time_to_string ( - GNUNET_TIME_absolute_get_remaining (keys_head->anchor), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + keys_head->anchor.abs_time), GNUNET_YES)); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); @@ -1146,26 +1174,32 @@ main (int argc, char **argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('s', + "section", + "SECTION", + "name of the configuration section prefix to use, default is 'taler'", + §ion), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), - GNUNET_GETOPT_option_absolute_time ('t', - "time", - "TIMESTAMP", - "pretend it is a different time for the update", - &now_tmp), + GNUNET_GETOPT_option_timestamp ('t', + "time", + "TIMESTAMP", + "pretend it is a different time for the update", + &now_tmp), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; /* Restrict permissions for the key files that we create. */ (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH); - + section = GNUNET_strdup ("taler-exchange"); /* force linker to link against libtalerutil; if we do not do this, the linker may "optimize" libtalerutil away and skip #TALER_OS_init(), which we do need */ TALER_OS_init (); - now = now_tmp = GNUNET_TIME_absolute_get (); - ret = GNUNET_PROGRAM_run (argc, argv, + now_tmp = now = GNUNET_TIME_timestamp_get (); + ret = GNUNET_PROGRAM_run (argc, + argv, "taler-exchange-secmod-eddsa", "Handle private EDDSA key operations for a Taler exchange", options, diff --git a/src/util/taler-exchange-secmod-eddsa.conf b/src/util/taler-exchange-secmod-eddsa.conf index ea09f0334..0cb4a4ffc 100644 --- a/src/util/taler-exchange-secmod-eddsa.conf +++ b/src/util/taler-exchange-secmod-eddsa.conf @@ -8,16 +8,16 @@ OVERLAP_DURATION = 5m # Where do we store the private keys. -KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-eddsa/keys +KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-eddsa/keys # Where does the helper listen for requests? -UNIXPATH = $TALER_RUNTIME_DIR/exchange-secmod-eddsa/server.sock +UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-eddsa/server.sock # Directory for clients. -CLIENT_DIR = $TALER_RUNTIME_DIR/exchange-secmod-eddsa/clients +CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-eddsa/clients # Where should the security module store its own private key? -SM_PRIV_KEY = ${TALER_DATA_HOME}/exchange-secmod-eddsa/secmod-private-key +SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-eddsa/secmod-private-key # For how long into the future do we pre-generate keys? LOOKAHEAD_SIGN = 1 year diff --git a/src/util/taler-exchange-secmod-eddsa.h b/src/util/taler-exchange-secmod-eddsa.h index 39054c414..c05d90a6c 100644 --- a/src/util/taler-exchange-secmod-eddsa.h +++ b/src/util/taler-exchange-secmod-eddsa.h @@ -54,7 +54,7 @@ struct TALER_CRYPTO_EddsaKeyAvailableNotification /** * When does the key become available? */ - struct GNUNET_TIME_AbsoluteNBO anchor_time; + struct GNUNET_TIME_TimestampNBO anchor_time; /** * How long is the key available after @e anchor_time? diff --git a/src/util/taler-exchange-secmod-rsa.c b/src/util/taler-exchange-secmod-rsa.c index 43109b5a4..c80e2e3c4 100644 --- a/src/util/taler-exchange-secmod-rsa.c +++ b/src/util/taler-exchange-secmod-rsa.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2021 Taler Systems SA + Copyright (C) 2014-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 @@ -36,7 +36,6 @@ #include "taler-exchange-secmod-rsa.h" #include <gcrypt.h> #include <pthread.h> -#include <sys/eventfd.h> #include "taler_error_codes.h" #include "taler_signatures.h" #include "secmod_common.h" @@ -98,7 +97,7 @@ struct DenominationKey /** * Time at which this key is supposed to become valid. */ - struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Timestamp anchor; /** * Generation when this key was created or revoked. @@ -165,6 +164,128 @@ struct Denomination /** + * A semaphore. + */ +struct Semaphore +{ + /** + * Mutex for the semaphore. + */ + pthread_mutex_t mutex; + + /** + * Condition variable for the semaphore. + */ + pthread_cond_t cv; + + /** + * Counter of the semaphore. + */ + unsigned int ctr; +}; + + +/** + * Job in a batch sign request. + */ +struct BatchJob; + +/** + * Handle for a thread that does work in batch signing. + */ +struct Worker +{ + /** + * Kept in a DLL. + */ + struct Worker *prev; + + /** + * Kept in a DLL. + */ + struct Worker *next; + + /** + * Job this worker should do next. + */ + struct BatchJob *job; + + /** + * Semaphore to signal the worker that a job is available. + */ + struct Semaphore sem; + + /** + * Handle for this thread. + */ + pthread_t pt; + + /** + * Set to true if the worker should terminate. + */ + bool do_shutdown; +}; + + +/** + * Job in a batch sign request. + */ +struct BatchJob +{ + /** + * Request we are working on. + */ + const struct TALER_CRYPTO_SignRequest *sr; + + /** + * Thread doing the work. + */ + struct Worker *worker; + + /** + * Result with the signature. + */ + struct GNUNET_CRYPTO_RsaSignature *rsa_signature; + + /** + * Semaphore to signal that the job is finished. + */ + struct Semaphore sem; + + /** + * Computation status. + */ + enum TALER_ErrorCode ec; + +}; + + +/** + * Head of DLL of workers ready for more work. + */ +static struct Worker *worker_head; + +/** + * Tail of DLL of workers ready for more work. + */ +static struct Worker *worker_tail; + +/** + * Lock for manipulating the worker DLL. + */ +static pthread_mutex_t worker_lock; + +/** + * Total number of workers that were started. + */ +static unsigned int workers; + +/** + * Semaphore used to grab a worker. + */ +static struct Semaphore worker_sem; + +/** * Return value from main(). */ static int global_ret; @@ -173,13 +294,13 @@ static int global_ret; * Time when the key update is executed. * Either the actual current time, or a pretended time. */ -static struct GNUNET_TIME_Absolute now; +static struct GNUNET_TIME_Timestamp now; /** * The time for the key update, as passed by the user * on the command line. */ -static struct GNUNET_TIME_Absolute now_tmp; +static struct GNUNET_TIME_Timestamp now_tmp; /** * Where do we store the keys? @@ -187,6 +308,13 @@ static struct GNUNET_TIME_Absolute now_tmp; static char *keydir; /** + * Name of the configuration section prefix to use. Usually either "taler-exchange" or + * "donau". The actual configuration section will then be + * "$SECTION-secmod-rsa". + */ +static char *section; + +/** * How much should coin creation (@e duration_withdraw) duration overlap * with the next denomination? Basically, the starting time of two * denominations is always @e duration_withdraw - #overlap_duration apart. @@ -229,11 +357,17 @@ static pthread_mutex_t keys_lock; */ static uint64_t key_gen; +/** + * Number of workers to launch. Note that connections to + * exchanges are NOT workers. + */ +static unsigned int max_workers = 16; + /** * Generate the announcement message for @a dk. * - * @param[in,out] denomination key to generate the announcement for + * @param[in,out] dk denomination key to generate the announcement for */ static void generate_response (struct DenominationKey *dk) @@ -257,7 +391,7 @@ generate_response (struct DenominationKey *dk) an->header.type = htons (TALER_HELPER_RSA_MT_AVAIL); an->pub_size = htons ((uint16_t) buf_len); an->section_name_len = htons ((uint16_t) nlen); - an->anchor_time = GNUNET_TIME_absolute_hton (dk->anchor); + an->anchor_time = GNUNET_TIME_timestamp_hton (dk->anchor); an->duration_withdraw = GNUNET_TIME_relative_hton (denom->duration_withdraw); TALER_exchange_secmod_rsa_sign (&dk->h_rsa, denom->section, @@ -267,129 +401,453 @@ generate_response (struct DenominationKey *dk) &an->secm_sig); an->secm_pub = TES_smpub; p = (void *) &an[1]; - memcpy (p, - buf, - buf_len); + GNUNET_memcpy (p, + buf, + buf_len); GNUNET_free (buf); - memcpy (p + buf_len, - denom->section, - nlen); + GNUNET_memcpy (p + buf_len, + denom->section, + nlen); dk->an = an; } /** - * Handle @a client request @a sr to create signature. Create the - * signature using the respective key and return the result to - * the client. + * Do the actual signing work. * - * @param client the client making the request - * @param sr the request details - * @return #GNUNET_OK on success + * @param h_rsa key to sign with + * @param bm blinded message to sign + * @param[out] rsa_signaturep set to the RSA signature + * @return #TALER_EC_NONE on success */ -static enum GNUNET_GenericReturnValue -handle_sign_request (struct TES_Client *client, - const struct TALER_CRYPTO_SignRequest *sr) +static enum TALER_ErrorCode +do_sign (const struct TALER_RsaPubHashP *h_rsa, + const struct GNUNET_CRYPTO_RsaBlindedMessage *bm, + struct GNUNET_CRYPTO_RsaSignature **rsa_signaturep) { struct DenominationKey *dk; - const void *blinded_msg = &sr[1]; - size_t blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr); struct GNUNET_CRYPTO_RsaSignature *rsa_signature; struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); dk = GNUNET_CONTAINER_multihashmap_get (keys, - &sr->h_rsa.hash); + &h_rsa->hash); if (NULL == dk) { - struct TALER_CRYPTO_SignFailure sf = { - .header.size = htons (sizeof (sr)), - .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE), - .ec = htonl (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN) - }; - GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Signing request failed, denomination key %s unknown\n", - GNUNET_h2s (&sr->h_rsa.hash)); - return TES_transmit (client->csock, - &sf.header); + GNUNET_h2s (&h_rsa->hash)); + return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; } - if (0 != - GNUNET_TIME_absolute_get_remaining (dk->anchor).rel_value_us) + if (GNUNET_TIME_absolute_is_future (dk->anchor.abs_time)) { /* it is too early */ - struct TALER_CRYPTO_SignFailure sf = { - .header.size = htons (sizeof (sr)), - .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE), - .ec = htonl (TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY) - }; - GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Signing request failed, denomination key %s is not yet valid\n", - GNUNET_h2s (&sr->h_rsa.hash)); - return TES_transmit (client->csock, - &sf.header); + GNUNET_h2s (&h_rsa->hash)); + return TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received request to sign over %u bytes with key %s\n", - (unsigned int) blinded_msg_size, - GNUNET_h2s (&sr->h_rsa.hash)); + (unsigned int) bm->blinded_msg_size, + GNUNET_h2s (&h_rsa->hash)); GNUNET_assert (dk->rc < UINT_MAX); dk->rc++; GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); rsa_signature = GNUNET_CRYPTO_rsa_sign_blinded (dk->denom_priv, - blinded_msg, - blinded_msg_size); + bm); GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); GNUNET_assert (dk->rc > 0); dk->rc--; GNUNET_assert (0 == pthread_mutex_unlock (&keys_lock)); if (NULL == rsa_signature) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Signing request failed, worker failed to produce signature\n"); + return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sending RSA signature after %s\n", + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_duration (now), + GNUNET_YES)); + *rsa_signaturep = rsa_signature; + return TALER_EC_NONE; +} + + +/** + * Generate error response that signing failed. + * + * @param client client to send response to + * @param ec error code to include + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +fail_sign (struct TES_Client *client, + enum TALER_ErrorCode ec) +{ + struct TALER_CRYPTO_SignFailure sf = { + .header.size = htons (sizeof (sf)), + .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE), + .ec = htonl (ec) + }; + + return TES_transmit (client->csock, + &sf.header); +} + + +/** + * Generate signature response. + * + * @param client client to send response to + * @param[in] rsa_signature signature to send, freed by this function + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +send_signature (struct TES_Client *client, + struct GNUNET_CRYPTO_RsaSignature *rsa_signature) +{ + struct TALER_CRYPTO_SignResponse *sr; + void *buf; + size_t buf_size; + size_t tsize; + enum GNUNET_GenericReturnValue ret; + + buf_size = GNUNET_CRYPTO_rsa_signature_encode (rsa_signature, + &buf); + GNUNET_CRYPTO_rsa_signature_free (rsa_signature); + tsize = sizeof (*sr) + buf_size; + GNUNET_assert (tsize < UINT16_MAX); + sr = GNUNET_malloc (tsize); + sr->header.size = htons (tsize); + sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE); + GNUNET_memcpy (&sr[1], + buf, + buf_size); + GNUNET_free (buf); + ret = TES_transmit (client->csock, + &sr->header); + GNUNET_free (sr); + return ret; +} + + +/** + * Handle @a client request @a sr to create signature. Create the + * signature using the respective key and return the result to + * the client. + * + * @param client the client making the request + * @param sr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_sign_request (struct TES_Client *client, + const struct TALER_CRYPTO_SignRequest *sr) +{ + struct GNUNET_CRYPTO_RsaBlindedMessage bm = { + .blinded_msg = (void *) &sr[1], + .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr) + }; + struct GNUNET_CRYPTO_RsaSignature *rsa_signature; + enum TALER_ErrorCode ec; + + ec = do_sign (&sr->h_rsa, + &bm, + &rsa_signature); + if (TALER_EC_NONE != ec) + { + return fail_sign (client, + ec); + } + return send_signature (client, + rsa_signature); +} + + +/** + * Initialize a semaphore @a sem with a value of @a val. + * + * @param[out] sem semaphore to initialize + * @param val initial value of the semaphore + */ +static void +sem_init (struct Semaphore *sem, + unsigned int val) +{ + GNUNET_assert (0 == + pthread_mutex_init (&sem->mutex, + NULL)); + GNUNET_assert (0 == + pthread_cond_init (&sem->cv, + NULL)); + sem->ctr = val; +} + + +/** + * Decrement semaphore, blocks until this is possible. + * + * @param[in,out] sem semaphore to decrement + */ +static void +sem_down (struct Semaphore *sem) +{ + GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex)); + while (0 == sem->ctr) + { + pthread_cond_wait (&sem->cv, + &sem->mutex); + } + sem->ctr--; + GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex)); +} + + +/** + * Increment semaphore, blocks until this is possible. + * + * @param[in,out] sem semaphore to decrement + */ +static void +sem_up (struct Semaphore *sem) +{ + GNUNET_assert (0 == pthread_mutex_lock (&sem->mutex)); + sem->ctr++; + GNUNET_assert (0 == pthread_mutex_unlock (&sem->mutex)); + pthread_cond_signal (&sem->cv); +} + + +/** + * Release resources used by @a sem. + * + * @param[in] sem semaphore to release (except the memory itself) + */ +static void +sem_done (struct Semaphore *sem) +{ + GNUNET_break (0 == pthread_cond_destroy (&sem->cv)); + GNUNET_break (0 == pthread_mutex_destroy (&sem->mutex)); +} + + +/** + * Main logic of a worker thread. Grabs work, does it, + * grabs more work. + * + * @param cls a `struct Worker *` + * @returns cls + */ +static void * +worker (void *cls) +{ + struct Worker *w = cls; + + while (true) + { + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + GNUNET_CONTAINER_DLL_insert (worker_head, + worker_tail, + w); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + sem_up (&worker_sem); + sem_down (&w->sem); + if (w->do_shutdown) + break; + { + struct BatchJob *bj = w->job; + const struct TALER_CRYPTO_SignRequest *sr = bj->sr; + struct GNUNET_CRYPTO_RsaBlindedMessage bm = { + .blinded_msg = (void *) &sr[1], + .blinded_msg_size = ntohs (sr->header.size) - sizeof (*sr) + }; + + bj->ec = do_sign (&sr->h_rsa, + &bm, + &bj->rsa_signature); + sem_up (&bj->sem); + w->job = NULL; + } + } + return w; +} + + +/** + * Start batch job @a bj to sign @a sr. + * + * @param sr signature request to answer + * @param[out] bj job data structure + */ +static void +start_job (const struct TALER_CRYPTO_SignRequest *sr, + struct BatchJob *bj) +{ + sem_init (&bj->sem, + 0); + bj->sr = sr; + sem_down (&worker_sem); + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + bj->worker = worker_head; + GNUNET_CONTAINER_DLL_remove (worker_head, + worker_tail, + bj->worker); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + bj->worker->job = bj; + sem_up (&bj->worker->sem); +} + + +/** + * Finish a job @a bj for a @a client. + * + * @param client who made the request + * @param[in,out] bj job to finish + */ +static void +finish_job (struct TES_Client *client, + struct BatchJob *bj) +{ + sem_down (&bj->sem); + sem_done (&bj->sem); + if (TALER_EC_NONE != bj->ec) + { + fail_sign (client, + bj->ec); + return; + } + GNUNET_assert (NULL != bj->rsa_signature); + send_signature (client, + bj->rsa_signature); + bj->rsa_signature = NULL; /* freed in send_signature */ +} + + +/** + * Handle @a client request @a sr to create a batch of signature. Creates the + * signatures using the respective key and return the results to the client. + * + * @param client the client making the request + * @param bsr the request details + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +handle_batch_sign_request (struct TES_Client *client, + const struct TALER_CRYPTO_BatchSignRequest *bsr) +{ + uint32_t bs = ntohl (bsr->batch_size); + uint16_t size = ntohs (bsr->header.size) - sizeof (*bsr); + const void *off = (const void *) &bsr[1]; + unsigned int idx = 0; + struct BatchJob jobs[bs]; + bool failure = false; + + if (bs > TALER_MAX_FRESH_COINS) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + while ( (bs > 0) && + (size > sizeof (struct TALER_CRYPTO_SignRequest)) ) + { + const struct TALER_CRYPTO_SignRequest *sr = off; + uint16_t s = ntohs (sr->header.size); + + if (s > size) + { + failure = true; + bs = idx; + break; + } + start_job (sr, + &jobs[idx++]); + off += s; + size -= s; + } + GNUNET_break_op (0 == size); + bs = GNUNET_MIN (bs, + idx); + for (unsigned int i = 0; i<bs; i++) + finish_job (client, + &jobs[i]); + if (failure) + { struct TALER_CRYPTO_SignFailure sf = { .header.size = htons (sizeof (sf)), - .header.type = htons (TALER_HELPER_RSA_MT_RES_SIGN_FAILURE), + .header.type = htons (TALER_HELPER_RSA_MT_RES_BATCH_FAILURE), .ec = htonl (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE) }; - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Signing request failed, worker failed to produce signature\n"); + GNUNET_break (0); return TES_transmit (client->csock, &sf.header); } + return GNUNET_OK; +} + +/** + * Start worker thread for batch processing. + * + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +start_worker (void) +{ + struct Worker *w; + + w = GNUNET_new (struct Worker); + sem_init (&w->sem, + 0); + if (0 != pthread_create (&w->pt, + NULL, + &worker, + w)) { - struct TALER_CRYPTO_SignResponse *sr; - void *buf; - size_t buf_size; - size_t tsize; - enum GNUNET_GenericReturnValue ret; + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "pthread_create"); + GNUNET_free (w); + return GNUNET_SYSERR; + } + workers++; + return GNUNET_OK; +} - buf_size = GNUNET_CRYPTO_rsa_signature_encode (rsa_signature, - &buf); - GNUNET_CRYPTO_rsa_signature_free (rsa_signature); - tsize = sizeof (*sr) + buf_size; - GNUNET_assert (tsize < UINT16_MAX); - sr = GNUNET_malloc (tsize); - sr->header.size = htons (tsize); - sr->header.type = htons (TALER_HELPER_RSA_MT_RES_SIGNATURE); - memcpy (&sr[1], - buf, - buf_size); - GNUNET_free (buf); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Sending RSA signature after %s\n", - GNUNET_STRINGS_relative_time_to_string ( - GNUNET_TIME_absolute_get_duration (now), - GNUNET_YES)); - ret = TES_transmit (client->csock, - &sr->header); - GNUNET_free (sr); - return ret; + +/** + * Stop all worker threads. + */ +static void +stop_workers (void) +{ + while (workers > 0) + { + struct Worker *w; + void *result; + + sem_down (&worker_sem); + GNUNET_assert (0 == pthread_mutex_lock (&worker_lock)); + w = worker_head; + GNUNET_CONTAINER_DLL_remove (worker_head, + worker_tail, + w); + GNUNET_assert (0 == pthread_mutex_unlock (&worker_lock)); + w->do_shutdown = true; + sem_up (&w->sem); + pthread_join (w->pt, + &result); + GNUNET_assert (result == w); + sem_done (&w->sem); + GNUNET_free (w); + workers--; } } @@ -428,13 +886,13 @@ setup_key (struct DenominationKey *dk, } buf_size = GNUNET_CRYPTO_rsa_private_key_encode (priv, &buf); - TALER_rsa_pub_hash (pub, - &dk->h_rsa); + GNUNET_CRYPTO_rsa_public_key_hash (pub, + &dk->h_rsa.hash); GNUNET_asprintf (&dk->filename, "%s/%s/%llu", keydir, denom->section, - (unsigned long long) (dk->anchor.abs_value_us + (unsigned long long) (dk->anchor.abs_time.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us)); if (GNUNET_OK != GNUNET_DISK_fn_write (dk->filename, @@ -454,7 +912,7 @@ setup_key (struct DenominationKey *dk, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setup fresh private key %s at %s in `%s' (generation #%llu)\n", GNUNET_h2s (&dk->h_rsa.hash), - GNUNET_STRINGS_absolute_time_to_string (dk->anchor), + GNUNET_TIME_timestamp2s (dk->anchor), dk->filename, (unsigned long long) key_gen); dk->denom_priv = priv; @@ -603,6 +1061,15 @@ rsa_work_dispatch (struct TES_Client *client, return handle_revoke_request ( client, (const struct TALER_CRYPTO_RevokeRequest *) hdr); + case TALER_HELPER_RSA_MT_REQ_BATCH_SIGN: + if (msize <= sizeof (struct TALER_CRYPTO_BatchSignRequest)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return handle_batch_sign_request ( + client, + (const struct TALER_CRYPTO_BatchSignRequest *) hdr); default: GNUNET_break_op (0); return GNUNET_SYSERR; @@ -635,6 +1102,8 @@ rsa_client_init (struct TES_Client *client) NULL != dk; dk = dk->next) { + GNUNET_assert (obs + ntohs (dk->an->header.size) + > obs); obs += ntohs (dk->an->header.size); } } @@ -648,9 +1117,11 @@ rsa_client_init (struct TES_Client *client) NULL != dk; dk = dk->next) { - memcpy (&buf[obs], - dk->an, - ntohs (dk->an->header.size)); + GNUNET_memcpy (&buf[obs], + dk->an, + ntohs (dk->an->header.size)); + GNUNET_assert (obs + ntohs (dk->an->header.size) + > obs); obs += ntohs (dk->an->header.size); } } @@ -747,16 +1218,20 @@ rsa_update_client_keys (struct TES_Client *client) .h_rsa = key->h_rsa }; - memcpy (&buf[obs], - &pn, - sizeof (pn)); + GNUNET_memcpy (&buf[obs], + &pn, + sizeof (pn)); + GNUNET_assert (obs + sizeof (pn) + > obs); obs += sizeof (pn); } else { - memcpy (&buf[obs], - key->an, - ntohs (key->an->header.size)); + GNUNET_memcpy (&buf[obs], + key->an, + ntohs (key->an->header.size)); + GNUNET_assert (obs + ntohs (key->an->header.size) + > obs); obs += ntohs (key->an->header.size); } } @@ -780,23 +1255,23 @@ rsa_update_client_keys (struct TES_Client *client) */ static enum GNUNET_GenericReturnValue create_key (struct Denomination *denom, - struct GNUNET_TIME_Absolute now) + struct GNUNET_TIME_Timestamp now) { struct DenominationKey *dk; - struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Timestamp anchor; - if (NULL == denom->keys_tail) - { - anchor = now; - } - else + anchor = now; + // FIXME: round down to multiple of 'anchor_round' value from configuration + if (NULL != denom->keys_tail) { - anchor = GNUNET_TIME_absolute_add (denom->keys_tail->anchor, - GNUNET_TIME_relative_subtract ( - denom->duration_withdraw, - overlap_duration)); - if (now.abs_value_us > anchor.abs_value_us) - anchor = now; + struct GNUNET_TIME_Absolute abs; + + abs = GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, + GNUNET_TIME_relative_subtract ( + denom->duration_withdraw, + overlap_duration)); + if (GNUNET_TIME_absolute_cmp (now.abs_time, <, abs)) + anchor = GNUNET_TIME_absolute_to_timestamp (abs); } dk = GNUNET_new (struct DenominationKey); dk->denom = denom; @@ -834,14 +1309,14 @@ denomination_action_time (const struct Denomination *denom) return GNUNET_TIME_UNIT_ZERO_ABS; tt = GNUNET_TIME_absolute_subtract ( GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (tail->anchor, + GNUNET_TIME_absolute_add (tail->anchor.abs_time, denom->duration_withdraw), lookahead_sign), overlap_duration); if (head->rc > 0) return tt; /* head expiration does not count due to rc > 0 */ return GNUNET_TIME_absolute_min ( - GNUNET_TIME_absolute_add (head->anchor, + GNUNET_TIME_absolute_add (head->anchor.abs_time, denom->duration_withdraw), tt); } @@ -859,15 +1334,28 @@ denomination_action_time (const struct Denomination *denom) */ static enum GNUNET_GenericReturnValue update_keys (struct Denomination *denom, - struct GNUNET_TIME_Absolute now, + struct GNUNET_TIME_Timestamp now, bool *wake) { /* create new denomination keys */ + if (NULL != denom->keys_tail) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Updating keys of denomination `%s', last key %s valid for another %s\n", + denom->section, + GNUNET_h2s (&denom->keys_tail->h_rsa.hash), + GNUNET_TIME_relative2s ( + GNUNET_TIME_absolute_get_remaining ( + GNUNET_TIME_absolute_subtract ( + GNUNET_TIME_absolute_add ( + denom->keys_tail->anchor.abs_time, + denom->duration_withdraw), + overlap_duration)), + GNUNET_YES)); while ( (NULL == denom->keys_tail) || GNUNET_TIME_absolute_is_past ( GNUNET_TIME_absolute_subtract ( GNUNET_TIME_absolute_subtract ( - GNUNET_TIME_absolute_add (denom->keys_tail->anchor, + GNUNET_TIME_absolute_add (denom->keys_tail->anchor.abs_time, denom->duration_withdraw), lookahead_sign), overlap_duration)) ) @@ -890,7 +1378,7 @@ update_keys (struct Denomination *denom, /* remove expired denomination keys */ while ( (NULL != denom->keys_head) && GNUNET_TIME_absolute_is_past - (GNUNET_TIME_absolute_add (denom->keys_head->anchor, + (GNUNET_TIME_absolute_add (denom->keys_head->anchor.abs_time, denom->duration_withdraw)) ) { struct DenominationKey *key = denom->keys_head; @@ -933,7 +1421,7 @@ update_keys (struct Denomination *denom, NULL != pos; pos = pos->next) { - if (denomination_action_time (pos).abs_value_us >= at.abs_value_us) + if (GNUNET_TIME_absolute_cmp (denomination_action_time (pos), >=, at)) break; before = pos; } @@ -956,12 +1444,13 @@ update_denominations (void *cls) { struct Denomination *denom; struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp t; bool wake = false; (void) cls; keygen_task = NULL; now = GNUNET_TIME_absolute_get (); - (void) GNUNET_TIME_round_abs (&now); + t = GNUNET_TIME_absolute_to_timestamp (now); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating denominations ...\n"); GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); @@ -969,7 +1458,7 @@ update_denominations (void *cls) denom = denom_head; if (GNUNET_OK != update_keys (denom, - now, + t, &wake)) return; } while (denom != denom_head); @@ -1002,7 +1491,7 @@ parse_key (struct Denomination *denom, char *anchor_s; char dummy; unsigned long long anchor_ll; - struct GNUNET_TIME_Absolute anchor; + struct GNUNET_TIME_Timestamp anchor; anchor_s = strrchr (filename, '/'); @@ -1024,8 +1513,10 @@ parse_key (struct Denomination *denom, filename); return; } - anchor.abs_value_us = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; - if (anchor_ll != anchor.abs_value_us / GNUNET_TIME_UNIT_SECONDS.rel_value_us) + anchor.abs_time.abs_value_us + = anchor_ll * GNUNET_TIME_UNIT_SECONDS.rel_value_us; + if (anchor_ll != anchor.abs_time.abs_value_us + / GNUNET_TIME_UNIT_SECONDS.rel_value_us) { /* Integer overflow. Bad, invalid filename. */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -1061,8 +1552,8 @@ parse_key (struct Denomination *denom, dk->denom = denom; dk->anchor = anchor; dk->filename = GNUNET_strdup (filename); - TALER_rsa_pub_hash (pub, - &dk->h_rsa); + GNUNET_CRYPTO_rsa_public_key_hash (pub, + &dk->h_rsa.hash); dk->denom_pub = pub; generate_response (dk); if (GNUNET_OK != @@ -1087,7 +1578,9 @@ parse_key (struct Denomination *denom, NULL != pos; pos = pos->next) { - if (pos->anchor.abs_value_us > anchor.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (pos->anchor, + >, + anchor)) break; before = pos; } @@ -1143,13 +1636,12 @@ import_key (void *cls, } fd = open (filename, - O_CLOEXEC); + O_RDONLY | O_CLOEXEC); if (-1 == fd) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "open", filename); - GNUNET_break (0 == close (fd)); return GNUNET_OK; } if (0 != fstat (fd, @@ -1158,6 +1650,7 @@ import_key (void *cls, GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "stat", filename); + GNUNET_break (0 == close (fd)); return GNUNET_OK; } if (! S_ISREG (sbuf.st_mode)) @@ -1237,7 +1730,11 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, struct Denomination *denom) { unsigned long long rsa_keysize; + char *secname; + GNUNET_asprintf (&secname, + "%s-secmod-rsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, ct, @@ -1247,16 +1744,18 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, ct, "DURATION_WITHDRAW"); + GNUNET_free (secname); return GNUNET_SYSERR; } - GNUNET_TIME_round_rel (&denom->duration_withdraw); - if (overlap_duration.rel_value_us >= - denom->duration_withdraw.rel_value_us) + if (GNUNET_TIME_relative_cmp (overlap_duration, + >=, + denom->duration_withdraw)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + section, "OVERLAP_DURATION", "Value given must be smaller than value for DURATION_WITHDRAW!"); + GNUNET_free (secname); return GNUNET_SYSERR; } if (GNUNET_OK != @@ -1268,6 +1767,7 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, ct, "RSA_KEYSIZE"); + GNUNET_free (secname); return GNUNET_SYSERR; } if ( (rsa_keysize > 4 * 2048) || @@ -1277,8 +1777,10 @@ parse_denomination_cfg (const struct GNUNET_CONFIGURATION_Handle *cfg, ct, "RSA_KEYSIZE", "Given RSA keysize outside of permitted range [1024,8192]\n"); + GNUNET_free (secname); return GNUNET_SYSERR; } + GNUNET_free (secname); denom->rsa_keysize = (unsigned int) rsa_keysize; denom->section = GNUNET_strdup (ct); return GNUNET_OK; @@ -1299,7 +1801,7 @@ struct LoadContext /** * Current time to use. */ - struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Timestamp t; /** * Status, to be set to #GNUNET_SYSERR on failure @@ -1322,6 +1824,7 @@ load_denominations (void *cls, struct LoadContext *ctx = cls; struct Denomination *denom; bool wake = true; + char *cipher; if ( (0 != strncasecmp (denomination_alias, "coin_", @@ -1330,6 +1833,23 @@ load_denominations (void *cls, "coin-", strlen ("coin-"))) ) return; /* not a denomination type definition */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (ctx->cfg, + denomination_alias, + "CIPHER", + &cipher)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + denomination_alias, + "CIPHER"); + return; + } + if (0 != strcmp (cipher, "RSA")) + { + GNUNET_free (cipher); + return; /* Ignore denominations of other types than CS */ + } + GNUNET_free (cipher); denom = GNUNET_new (struct Denomination); if (GNUNET_OK != parse_denomination_cfg (ctx->cfg, @@ -1361,7 +1881,7 @@ load_denominations (void *cls, denom_tail, denom); update_keys (denom, - ctx->now, + ctx->t, &wake); } @@ -1375,31 +1895,36 @@ load_denominations (void *cls, static enum GNUNET_GenericReturnValue load_durations (const struct GNUNET_CONFIGURATION_Handle *cfg) { + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-rsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, - "taler-exchange-secmod-rsa", + secname, "OVERLAP_DURATION", &overlap_duration)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + secname, "OVERLAP_DURATION"); + GNUNET_free (secname); return GNUNET_SYSERR; } - GNUNET_TIME_round_rel (&overlap_duration); - if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, - "taler-exchange-secmod-rsa", + secname, "LOOKAHEAD_SIGN", &lookahead_sign)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + secname, "LOOKAHEAD_SIGN"); + GNUNET_free (secname); return GNUNET_SYSERR; } - GNUNET_TIME_round_rel (&lookahead_sign); + GNUNET_free (secname); return GNUNET_OK; } @@ -1419,6 +1944,8 @@ do_shutdown (void *cls) GNUNET_SCHEDULER_cancel (keygen_task); keygen_task = NULL; } + stop_workers (); + sem_done (&worker_sem); } @@ -1441,10 +1968,12 @@ run (void *cls, .updater = rsa_update_client_keys, .init = rsa_client_init }; + char *secname; + (void) cls; (void) args; (void) cfgfile; - if (now.abs_value_us != now_tmp.abs_value_us) + if (GNUNET_TIME_timestamp_cmp (now, !=, now_tmp)) { /* The user gave "--now", use it! */ now = now_tmp; @@ -1452,34 +1981,65 @@ run (void *cls, else { /* get current time again, we may be timetraveling! */ - now = GNUNET_TIME_absolute_get (); + now = GNUNET_TIME_timestamp_get (); } - GNUNET_TIME_round_abs (&now); + GNUNET_asprintf (&secname, + "%s-secmod-rsa", + section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, - "taler-exchange-secmod-rsa", + secname, "KEY_DIR", &keydir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "taler-exchange-secmod-rsa", + secname, "KEY_DIR"); + GNUNET_free (secname); global_ret = EXIT_NOTCONFIGURED; return; } + GNUNET_free (secname); if (GNUNET_OK != load_durations (cfg)) { global_ret = EXIT_NOTCONFIGURED; return; } - global_ret = TES_listen_start (cfg, - "taler-exchange-secmod-rsa", - &cb); + { + char *secname; + + GNUNET_asprintf (&secname, + "%s-secmod-rsa", + section); + global_ret = TES_listen_start (cfg, + secname, + &cb); + GNUNET_free (secname); + } if (0 != global_ret) return; + sem_init (&worker_sem, + 0); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); + if (0 == max_workers) + { + long lret; + + lret = sysconf (_SC_NPROCESSORS_CONF); + if (lret <= 0) + lret = 1; + max_workers = (unsigned int) lret; + } + + for (unsigned int i = 0; i<max_workers; i++) + if (GNUNET_OK != + start_worker ()) + { + GNUNET_SCHEDULER_shutdown (); + return; + } /* Load denominations */ keys = GNUNET_CONTAINER_multihashmap_create (65536, GNUNET_YES); @@ -1487,10 +2047,9 @@ run (void *cls, struct LoadContext lc = { .cfg = cfg, .ret = GNUNET_OK, - .now = now + .t = now }; - (void) GNUNET_TIME_round_abs (&lc.now); GNUNET_assert (0 == pthread_mutex_lock (&keys_lock)); GNUNET_CONFIGURATION_iterate_sections (cfg, &load_denominations, @@ -1505,10 +2064,9 @@ run (void *cls, } if (NULL == denom_head) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "No denominations configured\n"); - global_ret = EXIT_NOTCONFIGURED; - GNUNET_SCHEDULER_shutdown (); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No RSA denominations configured\n"); + TES_wake_clients (); return; } /* start job to keep keys up-to-date; MUST be run before the #listen_task, @@ -1532,25 +2090,35 @@ main (int argc, char **argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_string ('s', + "section", + "SECTION", + "name of the configuration section prefix to use, default is 'taler'", + §ion), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), - GNUNET_GETOPT_option_absolute_time ('t', - "time", - "TIMESTAMP", - "pretend it is a different time for the update", - &now_tmp), + GNUNET_GETOPT_option_timestamp ('t', + "time", + "TIMESTAMP", + "pretend it is a different time for the update", + &now_tmp), + GNUNET_GETOPT_option_uint ('w', + "workers", + "COUNT", + "use COUNT workers for parallel processing of batch requests", + &max_workers), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; /* Restrict permissions for the key files that we create. */ (void) umask (S_IWGRP | S_IROTH | S_IWOTH | S_IXOTH); - + section = GNUNET_strdup ("taler-exchange"); /* force linker to link against libtalerutil; if we do not do this, the linker may "optimize" libtalerutil away and skip #TALER_OS_init(), which we do need */ TALER_OS_init (); - now = now_tmp = GNUNET_TIME_absolute_get (); + now_tmp = now = GNUNET_TIME_timestamp_get (); ret = GNUNET_PROGRAM_run (argc, argv, "taler-exchange-secmod-rsa", "Handle private RSA key operations for a Taler exchange", diff --git a/src/util/taler-exchange-secmod-rsa.conf b/src/util/taler-exchange-secmod-rsa.conf index dfa87f050..978c40258 100644 --- a/src/util/taler-exchange-secmod-rsa.conf +++ b/src/util/taler-exchange-secmod-rsa.conf @@ -5,19 +5,22 @@ # wallets picking one key and then due to network latency # another key being valid. The DURATION_WITHDRAW period # must be longer than this value. -OVERLAP_DURATION = 5 m +OVERLAP_DURATION = 0 m # Where do we store the generated private keys. -KEY_DIR = ${TALER_DATA_HOME}/exchange-secmod-rsa/keys +KEY_DIR = ${TALER_DATA_HOME}exchange-secmod-rsa/keys # Where does the helper listen for requests? -UNIXPATH = $TALER_RUNTIME_DIR/exchange-secmod-rsa/server.sock +UNIXPATH = ${TALER_RUNTIME_DIR}exchange-secmod-rsa/server.sock # Directory for clients. -CLIENT_DIR = $TALER_RUNTIME_DIR/exchange-secmod-rsa/clients +CLIENT_DIR = ${TALER_RUNTIME_DIR}exchange-secmod-rsa/clients # Where should the security module store its own private key? -SM_PRIV_KEY = ${TALER_DATA_HOME}/exchange-secmod-rsa/secmod-private-key +SM_PRIV_KEY = ${TALER_DATA_HOME}exchange-secmod-rsa/secmod-private-key # For how long into the future do we pre-generate keys? LOOKAHEAD_SIGN = 1 year + +# Round down anchor key start date to multiples of this time. +ANCHOR_ROUND = 1 ms
\ No newline at end of file diff --git a/src/util/taler-exchange-secmod-rsa.h b/src/util/taler-exchange-secmod-rsa.h index b0fdfbd96..ffbceb48e 100644 --- a/src/util/taler-exchange-secmod-rsa.h +++ b/src/util/taler-exchange-secmod-rsa.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2020 Taler Systems SA + Copyright (C) 2020-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 @@ -24,14 +24,17 @@ #define TALER_HELPER_RSA_MT_PURGE 1 #define TALER_HELPER_RSA_MT_AVAIL 2 +#define TALER_HELPER_RSA_MT_REQ_BATCH_SIGN 3 #define TALER_HELPER_RSA_MT_REQ_INIT 4 #define TALER_HELPER_RSA_MT_REQ_SIGN 5 #define TALER_HELPER_RSA_MT_REQ_REVOKE 6 #define TALER_HELPER_RSA_MT_RES_SIGNATURE 7 #define TALER_HELPER_RSA_MT_RES_SIGN_FAILURE 8 +#define TALER_HELPER_RSA_MT_RES_BATCH_FAILURE 9 + +#define TALER_HELPER_RSA_SYNCED 10 -#define TALER_HELPER_RSA_SYNCED 9 GNUNET_NETWORK_STRUCT_BEGIN @@ -59,7 +62,7 @@ struct TALER_CRYPTO_RsaKeyAvailableNotification /** * When does the key become available? */ - struct GNUNET_TIME_AbsoluteNBO anchor_time; + struct GNUNET_TIME_TimestampNBO anchor_time; /** * How long is the key available after @e anchor_time? @@ -73,7 +76,7 @@ struct TALER_CRYPTO_RsaKeyAvailableNotification /** * Signature affirming the announcement, of - * purpose #TALER_SIGNATURE_SM_DENOMINATION_KEY. + * purpose #TALER_SIGNATURE_SM_RSA_DENOMINATION_KEY. */ struct TALER_SecurityModuleSignatureP secm_sig; @@ -133,6 +136,28 @@ struct TALER_CRYPTO_SignRequest /** + * Message sent if a batch of signatures is requested. + */ +struct TALER_CRYPTO_BatchSignRequest +{ + /** + * Type is #TALER_HELPER_RSA_MT_REQ_BATCH_SIGN. + */ + struct GNUNET_MessageHeader header; + + /** + * Number of signatures to create, in NBO. + */ + uint32_t batch_size; + + /* + * Followed by @e batch_size sign requests. + */ + +}; + + +/** * Message sent if a key was revoked. */ struct TALER_CRYPTO_RevokeRequest diff --git a/src/util/test_age_restriction.c b/src/util/test_age_restriction.c new file mode 100644 index 000000000..61499e5e0 --- /dev/null +++ b/src/util/test_age_restriction.c @@ -0,0 +1,442 @@ +/* + 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 <gnunet/gnunet_common.h> + +/** + * Encodes the age mask into a string, like "8:10:12:14:16:18:21" + * + * @param mask Age mask + * @return String representation of the age mask, allocated by GNUNET_malloc. + * Can be used as value in the TALER config. + */ +char * +age_mask_to_string ( + const struct TALER_AgeMask *m) +{ + uint32_t bits = m->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 +test_groups (void) +{ + struct + { + uint32_t bits; + uint8_t group[33]; + } test[] = { + { + .bits = + 1 | 1 << 5 | 1 << 13 | 1 << 23, + + .group = { 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 } + + + }, + { + .bits = + 1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21, + .group = { 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, + 2, 2, + 3, 3, + 4, 4, + 5, 5, + 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7} + + + } + }; + + for (uint8_t t = 0; t < sizeof(test) / sizeof(test[0]); t++) + { + struct TALER_AgeMask mask = {.bits = test[t].bits}; + + for (uint8_t i = 0; i < 32; i++) + { + uint8_t r = TALER_get_age_group (&mask, i); + char *m = age_mask_to_string (&mask); + + printf ("TALER_get_age_group(%s, %2d) = %d vs %d (exp)\n", + m, + i, + r, + test[t].group[i]); + + if (test[t].group[i] != r) + return GNUNET_SYSERR; + + GNUNET_free (m); + } + } + + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +test_dates (void) +{ + struct TALER_AgeMask mask = { + .bits = 1 | 1 << 5 | 1 << 9 | 1 << 13 | 1 << 17 | 1 << 21 + }; + struct + { + char *date; + uint32_t expected; + enum GNUNET_GenericReturnValue ret; + } + test [] = { + {.date = "abcd-00-00", .expected = 0, .ret = GNUNET_SYSERR}, + {.date = "1900-00-01", .expected = 0, .ret = GNUNET_SYSERR}, + {.date = "19000001", .expected = 0, .ret = GNUNET_SYSERR}, + {.date = "2001-33-05", .expected = 0, .ret = GNUNET_SYSERR}, + {.date = "2001-33-35", .expected = 0, .ret = GNUNET_SYSERR}, + + {.date = "1900-00-00", .expected = 0, .ret = GNUNET_OK}, + {.date = "2001-00-00", .expected = 0, .ret = GNUNET_OK}, + {.date = "2001-03-00", .expected = 0, .ret = GNUNET_OK}, + {.date = "2001-03-05", .expected = 0, .ret = GNUNET_OK}, + + /* These dates should be far enough for the near future so that + * the expected values are correct. Will need adjustment in 2044 :) */ + {.date = "2022-11-26", .expected = 19322, .ret = GNUNET_OK }, + {.date = "2022-11-27", .expected = 19323, .ret = GNUNET_OK }, + {.date = "2023-06-26", .expected = 19534, .ret = GNUNET_OK }, + {.date = "2023-06-01", .expected = 19509, .ret = GNUNET_OK }, + {.date = "2023-06-00", .expected = 19509, .ret = GNUNET_OK }, + {.date = "2023-01-01", .expected = 19358, .ret = GNUNET_OK }, + {.date = "2023-00-00", .expected = 19358, .ret = GNUNET_OK }, + + /* Special case: .date == NULL meands birthday == current date, which + * should be 21 years in the future. We will set these values below in the + * loop */ + {.date = NULL, .expected = 0, .ret = GNUNET_OK }, + }; + char buf[256] = {0}; + + for (uint8_t t = 0; t < sizeof(test) / sizeof(test[0]); t++) + { + uint32_t d; + enum GNUNET_GenericReturnValue ret; + char *date = test[t].date; + + if (NULL == test[t].date) + { + /* Special case: We set .date to the current date. */ + time_t tn; + struct tm now; + + time (&tn); + localtime_r (&tn, &now); + strftime (buf, sizeof(buf), "%Y-%m-%d", &now); + date = &buf[0]; + + /* The expected value is the number of days since 1970-01-01, + * counted simplistically */ + test[t].expected = timegm (&now) / 60 / 60 / 24; + } + + ret = TALER_parse_coarse_date (date, + &mask, + &d); + if (ret != test[t].ret) + { + printf ( + "dates[%d] for date `%s` expected parser to return: %d, got: %d\n", + t, date, test[t].ret, ret); + return GNUNET_SYSERR; + } + + if (ret == GNUNET_SYSERR) + continue; + + if (d != test[t].expected) + { + printf ( + "dates[%d] for date `%s` expected value %d, but got %d\n", + t, date, test[t].expected, d); + return GNUNET_SYSERR; + } + + printf ("dates[%d] for date `%s` got expected value %d\n", + t, date, d); + } + + printf ("done with dates\n"); + + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +test_lowest (void) +{ + struct TALER_AgeMask mask = { + .bits = 1 | 1 << 5 | 1 << 9 | 1 << 13 | 1 << 17 | 1 << 21 + }; + + struct { uint8_t age; uint8_t expected; } + test [] = { + {.age = 1, .expected = 0 }, + {.age = 2, .expected = 0 }, + {.age = 3, .expected = 0 }, + {.age = 4, .expected = 0 }, + {.age = 5, .expected = 5 }, + {.age = 6, .expected = 5 }, + {.age = 7, .expected = 5 }, + {.age = 8, .expected = 5 }, + {.age = 9, .expected = 9 }, + {.age = 10, .expected = 9 }, + {.age = 11, .expected = 9 }, + {.age = 12, .expected = 9 }, + {.age = 13, .expected = 13 }, + {.age = 14, .expected = 13 }, + {.age = 15, .expected = 13 }, + {.age = 16, .expected = 13 }, + {.age = 17, .expected = 17 }, + {.age = 18, .expected = 17 }, + {.age = 19, .expected = 17 }, + {.age = 20, .expected = 17 }, + {.age = 21, .expected = 21 }, + {.age = 22, .expected = 21 }, + }; + + for (uint8_t n = 0; n < sizeof(test) / sizeof(test[0]); n++) + { + uint8_t l = TALER_get_lowest_age (&mask, test[n].age); + printf ("lowest[%d] for age %d, expected lowest: %d, got: %d\n", + n, test[n].age, test[n].expected, l); + if (test[n].expected != l) + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +test_adult (void) +{ + struct { struct TALER_AgeMask mask; uint8_t expected; } + test[] = { + { .mask = {.bits = 1 | 1 << 2}, + .expected = 2 }, + { .mask = {.bits = 1 | 1 << 2 | 1 << 3}, + .expected = 3 }, + { .mask = {.bits = 1 | 1 << 3}, + .expected = 3 }, + { .mask = {.bits = 1 | 1 << 22}, + .expected = 22 }, + { .mask = {.bits = 1 | 1 << 10 | 1 << 16 | 1 << 22}, + .expected = 22 }, + }; + for (uint8_t n = 0; n < sizeof(test) / sizeof(test[0]); n++) + { + uint8_t l = TALER_adult_age (&test[n].mask); + printf ("adult[%d] for mask %s, expected: %d, got: %d\n", + n, TALER_age_mask_to_string (&test[n].mask), test[n].expected, l); + if (test[n].expected != l) + return GNUNET_SYSERR; + } + printf ("done with adult\n"); + + return GNUNET_OK; +} + + +static struct TALER_AgeMask age_mask = { + .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12 | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21 +}; + +enum GNUNET_GenericReturnValue +test_attestation (void) +{ + uint8_t age; + for (age = 0; age < 33; age++) + { + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeCommitmentProof acp[3] = {0}; + struct TALER_AgeAttestation at = {0}; + uint8_t age_group = TALER_get_age_group (&age_mask, age); + struct GNUNET_HashCode seed; + + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + + TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp[0]); + printf ( + "commit(age:%d); proof.num: %ld; age_group: %d\n", + age, + acp[0].proof.num, + age_group); + + /* Also derive two more commitments right away */ + for (uint8_t i = 0; i<2; i++) + { + struct GNUNET_HashCode salt; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &salt, + sizeof (salt)); + 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 = TALER_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) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + + if (min_group > age_group && + GNUNET_NO != ret) + { + GNUNET_break (0); + ret = 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) + { + GNUNET_break (0); + break; + } + } + + TALER_age_commitment_proof_free (&acp[i]); + } + + if (GNUNET_SYSERR == ret) + { + GNUNET_break (0); + return ret; + } + } + return GNUNET_OK; +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + GNUNET_log_setup ("test-age-restriction", + "INFO", + NULL); + if (GNUNET_OK != test_groups ()) + return 1; + if (GNUNET_OK != test_lowest ()) + return 2; + if (GNUNET_OK != test_attestation ()) + { + GNUNET_break (0); + return 3; + } + if (GNUNET_OK != test_dates ()) + return 4; + if (GNUNET_OK != test_adult ()) + return 5; + return 0; +} + + +/* end of test_age_restriction.c */ diff --git a/src/util/test_amount.c b/src/util/test_amount.c index 1af383dcc..57d73b14f 100644 --- a/src/util/test_amount.c +++ b/src/util/test_amount.c @@ -21,7 +21,6 @@ */ #include "platform.h" #include "taler_util.h" -#include "taler_amount_lib.h" int @@ -79,31 +78,31 @@ main (int argc, /* test conversion with leading zero in fraction */ GNUNET_assert (GNUNET_OK == - TALER_string_to_amount ("eur:0.02", + TALER_string_to_amount ("EUR:0.02", &a2)); - GNUNET_assert (0 == strcasecmp ("eur", + GNUNET_assert (0 == strcasecmp ("EUR", a2.currency)); GNUNET_assert (0 == a2.value); GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 2 == a2.fraction); c = TALER_amount_to_string (&a2); - GNUNET_assert (0 == strcmp ("eur:0.02", - c)); + GNUNET_assert (0 == strcasecmp ("EUR:0.02", + c)); GNUNET_free (c); /* test conversion with leading space and with fraction */ GNUNET_assert (GNUNET_OK == - TALER_string_to_amount (" eur:4.12", + TALER_string_to_amount (" EUR:4.12", &a2)); - GNUNET_assert (0 == strcasecmp ("eur", + GNUNET_assert (0 == strcasecmp ("EUR", a2.currency)); GNUNET_assert (4 == a2.value); GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 100 * 12 == a2.fraction); /* test use of local currency */ GNUNET_assert (GNUNET_OK == - TALER_string_to_amount (" *LOCAL:4444.1000", + TALER_string_to_amount (" LOCAL:4444.1000", &a3)); - GNUNET_assert (0 == strcasecmp ("*LOCAL", + GNUNET_assert (0 == strcasecmp ("LOCAL", a3.currency)); GNUNET_assert (4444 == a3.value); GNUNET_assert (TALER_AMOUNT_FRAC_BASE / 10 == a3.fraction); diff --git a/src/util/test_conversion.c b/src/util/test_conversion.c new file mode 100644 index 000000000..00cb35e72 --- /dev/null +++ b/src/util/test_conversion.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + (C) 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file util/test_conversion.c + * @brief Tests for conversion logic + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include <gnunet/gnunet_json_lib.h> + +/** + * Return value from main(). + */ +static int global_ret; + +/** + * Handle to our helper. + */ +static struct TALER_JSON_ExternalConversion *ec; + + +/** + * Type of a callback that receives a JSON @a result. + * + * @param cls closure + * @param status_type how did the process die + * @apram code termination status code from the process + * @param result some JSON result, NULL if we failed to get an JSON output + */ +static void +conv_cb (void *cls, + enum GNUNET_OS_ProcessStatusType status_type, + unsigned long code, + const json_t *result) +{ + json_t *expect; + + (void) cls; + (void) status_type; + ec = NULL; + global_ret = 3; + if (42 != code) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected return value from helper: %u\n", + (unsigned int) code); + return; + } + expect = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("foo", + "arg") + ); + if (1 == json_equal (expect, + result)) + { + global_ret = 0; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected JSON result\n"); + json_dumpf (result, + stderr, + JSON_INDENT (2)); + global_ret = 4; + } + json_decref (expect); +} + + +/** + * Function called on shutdown/CTRL-C. + * + * @param cls NULL + */ +static void +do_shutdown (void *cls) +{ + (void) cls; + if (NULL != ec) + { + GNUNET_break (0); + global_ret = 2; + TALER_JSON_external_conversion_stop (ec); + ec = NULL; + } +} + + +/** + * Main test function. + * + * @param cls NULL + */ +static void +run (void *cls) +{ + json_t *input; + + (void) cls; + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + input = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("key", + "foo") + ); + ec = TALER_JSON_external_conversion_start (input, + &conv_cb, + NULL, + "./test_conversion.sh", + "test_conversion.sh", + "arg", + NULL); + json_decref (input); + GNUNET_assert (NULL != ec); +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-conversion", + "WARNING", + NULL); + GNUNET_OS_init (TALER_project_data_default ()); + global_ret = 1; + GNUNET_SCHEDULER_run (&run, + NULL); + return global_ret; +} diff --git a/src/util/test_conversion.sh b/src/util/test_conversion.sh new file mode 100755 index 000000000..26e1a36d8 --- /dev/null +++ b/src/util/test_conversion.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +KEY=$(jq -r .key) +echo -n "{\"$KEY\":\"$1\"}" +exit 42 diff --git a/src/util/test_crypto.c b/src/util/test_crypto.c index 5ee06487b..2a2090952 100644 --- a/src/util/test_crypto.c +++ b/src/util/test_crypto.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2015, 2020, 2021 Taler Systems SA + (C) 2015, 2020-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 @@ -21,7 +21,6 @@ */ #include "platform.h" #include "taler_util.h" -#include "taler_crypto_lib.h" /** @@ -38,8 +37,21 @@ test_high_level (void) struct TALER_TransferPublicKeyP trans_pub; struct TALER_TransferSecretP secret; struct TALER_TransferSecretP secret2; - struct TALER_PlanchetSecretsP fc1; - struct TALER_PlanchetSecretsP fc2; + union GNUNET_CRYPTO_BlindingSecretP bks1; + union GNUNET_CRYPTO_BlindingSecretP bks2; + struct TALER_CoinSpendPrivateKeyP coin_priv1; + struct TALER_CoinSpendPrivateKeyP coin_priv2; + struct TALER_PlanchetMasterSecretP ps1; + struct TALER_PlanchetMasterSecretP ps2; + struct GNUNET_CRYPTO_BlindingInputValues bi = { + .cipher = GNUNET_CRYPTO_BSA_RSA + }; + struct TALER_ExchangeWithdrawValues alg1 = { + .blinding_inputs = &bi + }; + struct TALER_ExchangeWithdrawValues alg2 = { + .blinding_inputs = &bi + }; GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, @@ -62,19 +74,42 @@ test_high_level (void) GNUNET_assert (0 == GNUNET_memcmp (&secret, &secret2)); - TALER_planchet_setup_refresh (&secret, - 0, - &fc1); - TALER_planchet_setup_refresh (&secret, - 1, - &fc2); + TALER_transfer_secret_to_planchet_secret (&secret, + 0, + &ps1); + TALER_planchet_setup_coin_priv (&ps1, + &alg1, + &coin_priv1); + TALER_planchet_blinding_secret_create (&ps1, + &alg1, + &bks1); + TALER_transfer_secret_to_planchet_secret (&secret, + 1, + &ps2); + TALER_planchet_setup_coin_priv (&ps2, + &alg2, + &coin_priv2); + TALER_planchet_blinding_secret_create (&ps2, + &alg2, + &bks2); GNUNET_assert (0 != - GNUNET_memcmp (&fc1, - &fc2)); + GNUNET_memcmp (&ps1, + &ps2)); + GNUNET_assert (0 != + GNUNET_memcmp (&coin_priv1, + &coin_priv2)); + GNUNET_assert (0 != + GNUNET_memcmp (&bks1, + &bks2)); return 0; } +static struct TALER_AgeMask age_mask = { + .bits = 1 | 1 << 8 | 1 << 10 | 1 << 12 + | 1 << 14 | 1 << 16 | 1 << 18 | 1 << 21 +}; + /** * Test the basic planchet functionality of creating a fresh planchet * and extracting the respective signature. @@ -82,37 +117,88 @@ test_high_level (void) * @return 0 on success */ static int -test_planchets (void) +test_planchets_rsa (uint8_t age) { - struct TALER_PlanchetSecretsP ps; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; struct TALER_DenominationPrivateKey dk_priv; struct TALER_DenominationPublicKey dk_pub; + const struct TALER_ExchangeWithdrawValues *alg_values; struct TALER_PlanchetDetail pd; struct TALER_BlindedDenominationSignature blind_sig; struct TALER_FreshCoin coin; - struct TALER_CoinPubHash c_hash; + struct TALER_CoinPubHashP c_hash; + struct TALER_AgeCommitmentHash *ach = NULL; + struct TALER_AgeCommitmentHash ah = {0}; + + alg_values = TALER_denom_ewv_rsa_singleton (); + if (0 < age) + { + struct TALER_AgeCommitmentProof acp; + struct GNUNET_HashCode seed; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp); + TALER_age_commitment_hash (&acp.commitment, + &ah); + ach = &ah; + TALER_age_commitment_proof_free (&acp); + } + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &ps, + sizeof (ps)); + GNUNET_log_skip (1, GNUNET_YES); + GNUNET_assert (GNUNET_SYSERR == + TALER_denom_priv_create (&dk_priv, + &dk_pub, + GNUNET_CRYPTO_BSA_INVALID)); + GNUNET_log_skip (1, GNUNET_YES); + GNUNET_assert (GNUNET_SYSERR == + TALER_denom_priv_create (&dk_priv, + &dk_pub, + 42)); GNUNET_assert (GNUNET_OK == TALER_denom_priv_create (&dk_priv, &dk_pub, - TALER_DENOMINATION_RSA, + GNUNET_CRYPTO_BSA_RSA, 1024)); - TALER_planchet_setup_random (&ps); + TALER_planchet_setup_coin_priv (&ps, + alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + alg_values, + &bks); GNUNET_assert (GNUNET_OK == TALER_planchet_prepare (&dk_pub, - &ps, + alg_values, + &bks, + NULL, + &coin_priv, + ach, &c_hash, &pd)); GNUNET_assert (GNUNET_OK == TALER_denom_sign_blinded (&blind_sig, &dk_priv, - pd.coin_ev, - pd.coin_ev_size)); + false, + &pd.blinded_planchet)); + TALER_planchet_detail_free (&pd); GNUNET_assert (GNUNET_OK == TALER_planchet_to_coin (&dk_pub, &blind_sig, - &ps, + &bks, + &coin_priv, + ach, &c_hash, + alg_values, &coin)); TALER_blinded_denom_sig_free (&blind_sig); TALER_denom_sig_free (&coin.sig); @@ -122,6 +208,117 @@ test_planchets (void) } +/** + * Test the basic planchet functionality of creating a fresh planchet with CS denomination + * and extracting the respective signature. + * + * @return 0 on success + */ +static int +test_planchets_cs (uint8_t age) +{ + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + struct TALER_DenominationPrivateKey dk_priv; + struct TALER_DenominationPublicKey dk_pub; + struct TALER_PlanchetDetail pd; + struct TALER_CoinPubHashP c_hash; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + struct TALER_BlindedDenominationSignature blind_sig; + struct TALER_FreshCoin coin; + struct TALER_ExchangeWithdrawValues alg_values; + struct TALER_AgeCommitmentHash *ach = NULL; + + if (0 < age) + { + struct TALER_AgeCommitmentHash ah = {0}; + struct TALER_AgeCommitmentProof acp; + struct GNUNET_HashCode seed; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + TALER_age_restriction_commit (&age_mask, + age, + &seed, + &acp); + TALER_age_commitment_hash (&acp.commitment, + &ah); + ach = &ah; + TALER_age_commitment_proof_free (&acp); + } + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &ps, + sizeof (ps)); + GNUNET_assert (GNUNET_OK == + TALER_denom_priv_create (&dk_priv, + &dk_pub, + GNUNET_CRYPTO_BSA_CS)); + TALER_cs_withdraw_nonce_derive ( + &ps, + &nonce.cs_nonce); + // FIXME: define Taler abstraction for this: + alg_values.blinding_inputs + = GNUNET_CRYPTO_get_blinding_input_values (dk_priv.bsign_priv_key, + &nonce, + "rw"); + TALER_denom_pub_hash (&dk_pub, + &pd.denom_pub_hash); + TALER_planchet_setup_coin_priv (&ps, + &alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + &alg_values, + &bks); + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (&dk_pub, + &alg_values, + &bks, + &nonce, + &coin_priv, + ach, + &c_hash, + &pd)); + GNUNET_assert (GNUNET_OK == + TALER_denom_sign_blinded (&blind_sig, + &dk_priv, + false, + &pd.blinded_planchet)); + GNUNET_assert (GNUNET_OK == + TALER_planchet_to_coin (&dk_pub, + &blind_sig, + &bks, + &coin_priv, + ach, + &c_hash, + &alg_values, + &coin)); + TALER_blinded_denom_sig_free (&blind_sig); + TALER_denom_sig_free (&coin.sig); + TALER_denom_priv_free (&dk_priv); + TALER_denom_pub_free (&dk_pub); + return 0; +} + + +/** + * Test the basic planchet functionality of creating a fresh planchet + * and extracting the respective signature. + * Calls test_planchets_rsa and test_planchets_cs + * + * @return 0 on success + */ +static int +test_planchets (uint8_t age) +{ + if (0 != test_planchets_rsa (age)) + return -1; + return test_planchets_cs (age); +} + + static int test_exchange_sigs (void) { @@ -129,15 +326,24 @@ test_exchange_sigs (void) struct TALER_MasterPrivateKeyP priv; struct TALER_MasterPublicKeyP pub; struct TALER_MasterSignatureP sig; + json_t *rest; GNUNET_CRYPTO_eddsa_key_create (&priv.eddsa_priv); + rest = json_array (); + GNUNET_assert (NULL != rest); TALER_exchange_wire_signature_make (pt, + NULL, + rest, + rest, &priv, &sig); GNUNET_CRYPTO_eddsa_key_get_public (&priv.eddsa_priv, &pub.eddsa_pub); if (GNUNET_OK != TALER_exchange_wire_signature_check (pt, + NULL, + rest, + rest, &pub, &sig)) { @@ -147,12 +353,28 @@ test_exchange_sigs (void) if (GNUNET_OK == TALER_exchange_wire_signature_check ( "payto://x-taler-bank/localhost/Other", + NULL, + rest, + rest, + &pub, + &sig)) + { + GNUNET_break (0); + return 1; + } + if (GNUNET_OK == + TALER_exchange_wire_signature_check ( + pt, + "http://example.com/", + rest, + rest, &pub, &sig)) { GNUNET_break (0); return 1; } + json_decref (rest); return 0; } @@ -161,7 +383,7 @@ static int test_merchant_sigs (void) { const char *pt = "payto://x-taler-bank/localhost/Account"; - struct TALER_WireSalt salt; + struct TALER_WireSaltP salt; struct TALER_MerchantPrivateKeyP priv; struct TALER_MerchantPublicKeyP pub; struct TALER_MerchantSignatureP sig; @@ -211,20 +433,109 @@ test_merchant_sigs (void) } +static int +test_contracts (void) +{ + struct TALER_ContractDiffiePrivateP cpriv; + struct TALER_PurseContractPublicKeyP purse_pub; + struct TALER_PurseContractPrivateKeyP purse_priv; + void *econtract; + size_t econtract_size; + struct TALER_PurseMergePrivateKeyP mpriv_in; + struct TALER_PurseMergePrivateKeyP mpriv_out; + json_t *c; + + GNUNET_CRYPTO_ecdhe_key_create (&cpriv.ecdhe_priv); + GNUNET_CRYPTO_eddsa_key_create (&purse_priv.eddsa_priv); + GNUNET_CRYPTO_eddsa_key_get_public (&purse_priv.eddsa_priv, + &purse_pub.eddsa_pub); + memset (&mpriv_in, + 42, + sizeof (mpriv_in)); + c = json_pack ("{s:s}", "test", "value"); + GNUNET_assert (NULL != c); + TALER_CRYPTO_contract_encrypt_for_merge (&purse_pub, + &cpriv, + &mpriv_in, + c, + &econtract, + &econtract_size); + json_decref (c); + c = TALER_CRYPTO_contract_decrypt_for_merge (&cpriv, + &purse_pub, + econtract, + econtract_size, + &mpriv_out); + GNUNET_free (econtract); + if (NULL == c) + return 1; + json_decref (c); + if (0 != GNUNET_memcmp (&mpriv_in, + &mpriv_out)) + return 1; + return 0; +} + + +static int +test_attributes (void) +{ + struct TALER_AttributeEncryptionKeyP key; + void *eattr; + size_t eattr_size; + json_t *c; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &key, + sizeof (key)); + c = json_pack ("{s:s}", "test", "value"); + GNUNET_assert (NULL != c); + TALER_CRYPTO_kyc_attributes_encrypt (&key, + c, + &eattr, + &eattr_size); + json_decref (c); + c = TALER_CRYPTO_kyc_attributes_decrypt (&key, + eattr, + eattr_size); + GNUNET_free (eattr); + if (NULL == c) + { + GNUNET_break (0); + return 1; + } + GNUNET_assert (0 == + strcmp ("value", + json_string_value (json_object_get (c, + "test")))); + json_decref (c); + return 0; +} + + int main (int argc, const char *const argv[]) { (void) argc; (void) argv; + GNUNET_log_setup ("test-crypto", + "WARNING", + NULL); if (0 != test_high_level ()) return 1; - if (0 != test_planchets ()) + if (0 != test_planchets (0)) return 2; - if (0 != test_exchange_sigs ()) + if (0 != test_planchets (13)) return 3; - if (0 != test_merchant_sigs ()) + if (0 != test_exchange_sigs ()) return 4; + if (0 != test_merchant_sigs ()) + return 5; + if (0 != test_contracts ()) + return 6; + if (0 != test_attributes ()) + return 7; return 0; } diff --git a/src/util/test_helper_cs.c b/src/util/test_helper_cs.c new file mode 100644 index 000000000..93562e459 --- /dev/null +++ b/src/util/test_helper_cs.c @@ -0,0 +1,1177 @@ +/* + This file is part of TALER + (C) 2020, 2021, 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file util/test_helper_cs.c + * @brief Tests for CS crypto helper + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" + +/** + * Configuration has 1 minute duration and 5 minutes lookahead, but + * we do not get 'revocations' for expired keys. So this must be + * large enough to deal with key rotation during the runtime of + * the benchmark. + */ +#define MAX_KEYS 1024 + +/** + * How many random key revocations should we test? + */ +#define NUM_REVOKES 3 + +/** + * How many iterations of the successful signing test should we run? + */ +#define NUM_SIGN_TESTS 5 + +/** + * How many iterations of the successful signing test should we run + * during the benchmark phase? + */ +#define NUM_SIGN_PERFS 100 + +/** + * How many parallel clients should we use for the parallel + * benchmark? (> 500 may cause problems with the max open FD number limit). + */ +#define NUM_CORES 8 + +/** + * Number of keys currently in #keys. + */ +static unsigned int num_keys; + +/** + * Keys currently managed by the helper. + */ +struct KeyData +{ + /** + * Validity start point. + */ + struct GNUNET_TIME_Timestamp start_time; + + /** + * Key expires for signing at @e start_time plus this value. + */ + struct GNUNET_TIME_Relative validity_duration; + + /** + * Hash of the public key. + */ + struct TALER_CsPubHashP h_cs; + + /** + * Full public key. + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Is this key currently valid? + */ + bool valid; + + /** + * Did the test driver revoke this key? + */ + bool revoked; +}; + +/** + * Array of all the keys we got from the helper. + */ +static struct KeyData keys[MAX_KEYS]; + + +/** + * Release memory occupied by #keys. + */ +static void +free_keys (void) +{ + for (unsigned int i = 0; i<MAX_KEYS; i++) + if (keys[i].valid) + { + TALER_denom_pub_free (&keys[i].denom_pub); + keys[i].valid = false; + GNUNET_assert (num_keys > 0); + num_keys--; + } +} + + +/** + * Function called with information about available keys for signing. Usually + * only called once per key upon connect. Also called again in case a key is + * being revoked, in that case with an @a end_time of zero. Stores the keys + * status in #keys. + * + * @param cls closure, NULL + * @param section_name name of the denomination type in the configuration; + * NULL if the key has been revoked or purged + * @param start_time when does the key become available for signing; + * zero if the key has been revoked or purged + * @param validity_duration how long does the key remain available for signing; + * zero if the key has been revoked or purged + * @param h_cs hash of the @a denom_pub that is available (or was purged) + * @param bs_pub the public key itself, NULL if the key was revoked or purged + * @param sm_pub public key of the security module, NULL if the key was revoked or purged + * @param sm_sig signature from the security module, NULL if the key was revoked or purged + * The signature was already verified against @a sm_pub. + */ +static void +key_cb (void *cls, + const char *section_name, + struct GNUNET_TIME_Timestamp start_time, + struct GNUNET_TIME_Relative validity_duration, + const struct TALER_CsPubHashP *h_cs, + struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub, + const struct TALER_SecurityModulePublicKeyP *sm_pub, + const struct TALER_SecurityModuleSignatureP *sm_sig) +{ + (void) cls; + (void) sm_pub; + (void) sm_sig; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Key notification about key %s in `%s'\n", + GNUNET_h2s (&h_cs->hash), + section_name); + if (0 == validity_duration.rel_value_us) + { + bool found = false; + + GNUNET_break (NULL == bs_pub); + GNUNET_break (NULL == section_name); + for (unsigned int i = 0; i<MAX_KEYS; i++) + if (0 == GNUNET_memcmp (h_cs, + &keys[i].h_cs)) + { + keys[i].valid = false; + keys[i].revoked = false; + TALER_denom_pub_free (&keys[i].denom_pub); + GNUNET_assert (num_keys > 0); + num_keys--; + found = true; + break; + } + if (! found) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Error: helper announced expiration of unknown key!\n"); + + return; + } + + GNUNET_break (NULL != bs_pub); + for (unsigned int i = 0; i<MAX_KEYS; i++) + if (! keys[i].valid) + { + keys[i].valid = true; + keys[i].h_cs = *h_cs; + keys[i].start_time = start_time; + keys[i].validity_duration = validity_duration; + keys[i].denom_pub.bsign_pub_key + = GNUNET_CRYPTO_bsign_pub_incref (bs_pub); + num_keys++; + return; + } + /* too many keys! */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Error: received %d live keys from the service!\n", + MAX_KEYS + 1); +} + + +/** + * Test key revocation logic. + * + * @param dh handle to the helper + * @return 0 on success + */ +static int +test_revocation (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + struct timespec req = { + .tv_nsec = 250000000 + }; + + for (unsigned int i = 0; i<NUM_REVOKES; i++) + { + uint32_t off; + + off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + num_keys); + /* find index of key to revoke */ + for (unsigned int j = 0; j < MAX_KEYS; j++) + { + if (! keys[j].valid) + continue; + if (0 != off) + { + off--; + continue; + } + keys[j].revoked = true; + fprintf (stderr, + "Revoking key %s ...", + GNUNET_h2s (&keys[j].h_cs.hash)); + TALER_CRYPTO_helper_cs_revoke (dh, + &keys[j].h_cs); + for (unsigned int k = 0; k<1000; k++) + { + TALER_CRYPTO_helper_cs_poll (dh); + if (! keys[j].revoked) + break; + nanosleep (&req, NULL); + fprintf (stderr, "."); + } + if (keys[j].revoked) + { + fprintf (stderr, + "\nFAILED: timeout trying to revoke key %u\n", + j); + TALER_CRYPTO_helper_cs_disconnect (dh); + return 2; + } + fprintf (stderr, "\n"); + break; + } + } + return 0; +} + + +/** + * Test R derivation logic. + * + * @param dh handle to the helper + * @return 0 on success + */ +static int +test_r_derive (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + enum TALER_ErrorCode ec; + bool success = false; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + struct TALER_CoinPubHashP c_hash; + struct GNUNET_CRYPTO_BlindingInputValues bi = { + .cipher = GNUNET_CRYPTO_BSA_CS + }; + struct TALER_ExchangeWithdrawValues alg_values = { + .blinding_inputs = &bi + }; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + + TALER_planchet_master_setup_random (&ps); + for (unsigned int i = 0; i<MAX_KEYS; i++) + { + struct TALER_PlanchetDetail pd; + + if (! keys[i].valid) + continue; + GNUNET_assert (GNUNET_CRYPTO_BSA_CS == + keys[i].denom_pub.bsign_pub_key->cipher); + TALER_cs_withdraw_nonce_derive ( + &ps, + &nonce.cs_nonce); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting R derivation with key %s\n", + GNUNET_h2s (&keys[i].h_cs.hash)); + { + struct TALER_CRYPTO_CsDeriveRequest cdr = { + .h_cs = &keys[i].h_cs, + .nonce = &nonce.cs_nonce + }; + + ec = TALER_CRYPTO_helper_cs_r_derive ( + dh, + &cdr, + false, + &bi.details.cs_values); + } + switch (ec) + { + case TALER_EC_NONE: + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) + { + /* key worked too early */ + GNUNET_break (0); + return 4; + } + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + >, + keys[i].validity_duration)) + { + /* key worked too later */ + GNUNET_break (0); + return 5; + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received valid R for key %s\n", + GNUNET_h2s (&keys[i].h_cs.hash)); + TALER_planchet_setup_coin_priv (&ps, + &alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + &alg_values, + &bks); + GNUNET_assert (GNUNET_OK == + TALER_planchet_prepare (&keys[i].denom_pub, + &alg_values, + &bks, + &nonce, + &coin_priv, + NULL, /* no age commitment */ + &c_hash, + &pd)); + TALER_blinded_planchet_free (&pd.blinded_planchet); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Successfully prepared planchet"); + success = true; + break; + case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: + /* This 'failure' is expected, we're testing also for the + error handling! */ + if ( (GNUNET_TIME_relative_is_zero ( + GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time))) && + (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + <, + keys[i].validity_duration)) ) + { + /* key should have worked! */ + GNUNET_break (0); + return 6; + } + break; + default: + /* unexpected error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error %d\n", + ec); + return 7; + } + } + if (! success) + { + /* no valid key for signing found, also bad */ + GNUNET_break (0); + return 16; + } + + /* check R derivation does not work if the key is unknown */ + { + struct TALER_CsPubHashP rnd; + struct GNUNET_CRYPTO_CSPublicRPairP crp; + struct TALER_CRYPTO_CsDeriveRequest cdr = { + .h_cs = &rnd, + .nonce = &nonce.cs_nonce, + }; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &rnd, + sizeof (rnd)); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &nonce, + sizeof (nonce)); + ec = TALER_CRYPTO_helper_cs_r_derive (dh, + &cdr, + false, + &crp); + if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) + { + GNUNET_break (0); + return 17; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "R derivation with invalid key %s failed as desired\n", + GNUNET_h2s (&rnd.hash)); + } + return 0; +} + + +/** + * Test signing logic. + * + * @param dh handle to the helper + * @return 0 on success + */ +static int +test_signing (struct TALER_CRYPTO_CsDenominationHelper *dh) +{ + struct TALER_BlindedDenominationSignature ds; + enum TALER_ErrorCode ec; + bool success = false; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + struct TALER_CoinPubHashP c_hash; + struct GNUNET_CRYPTO_BlindingInputValues bi = { + .cipher = GNUNET_CRYPTO_BSA_CS + }; + struct TALER_ExchangeWithdrawValues alg_values = { + .blinding_inputs = &bi + }; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + + TALER_planchet_master_setup_random (&ps); + for (unsigned int i = 0; i<MAX_KEYS; i++) + { + if (! keys[i].valid) + continue; + { + struct TALER_PlanchetDetail pd; + struct TALER_CRYPTO_CsSignRequest csr; + struct TALER_CRYPTO_CsDeriveRequest cdr = { + .h_cs = &keys[i].h_cs, + .nonce = &nonce.cs_nonce + }; + + TALER_cs_withdraw_nonce_derive (&ps, + &nonce.cs_nonce); + ec = TALER_CRYPTO_helper_cs_r_derive ( + dh, + &cdr, + false, + &bi.details.cs_values); + if (TALER_EC_NONE != ec) + continue; + TALER_planchet_setup_coin_priv (&ps, + &alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + &alg_values, + &bks); + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[i].denom_pub, + &alg_values, + &bks, + &nonce, + &coin_priv, + NULL, /* no age commitment */ + &c_hash, + &pd)); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting signature with key %s\n", + GNUNET_h2s (&keys[i].h_cs.hash)); + csr.h_cs = &keys[i].h_cs; + csr.blinded_planchet + = &pd.blinded_planchet.blinded_message->details.cs_blinded_message; + ec = TALER_CRYPTO_helper_cs_sign ( + dh, + &csr, + false, + &ds); + TALER_blinded_planchet_free (&pd.blinded_planchet); + } + switch (ec) + { + case TALER_EC_NONE: + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) + { + /* key worked too early */ + GNUNET_break (0); + TALER_blinded_denom_sig_free (&ds); + return 4; + } + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + >, + keys[i].validity_duration)) + { + /* key worked too later */ + GNUNET_break (0); + TALER_blinded_denom_sig_free (&ds); + return 5; + } + { + struct TALER_FreshCoin coin; + + if (GNUNET_OK != + TALER_planchet_to_coin (&keys[i].denom_pub, + &ds, + &bks, + &coin_priv, + NULL, /* no age commitment */ + &c_hash, + &alg_values, + &coin)) + { + GNUNET_break (0); + TALER_blinded_denom_sig_free (&ds); + return 6; + } + TALER_blinded_denom_sig_free (&ds); + TALER_denom_sig_free (&coin.sig); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received valid signature for key %s\n", + GNUNET_h2s (&keys[i].h_cs.hash)); + success = true; + break; + case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: + /* This 'failure' is expected, we're testing also for the + error handling! */ + if ( (GNUNET_TIME_relative_is_zero ( + GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time))) && + (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + <, + keys[i].validity_duration)) ) + { + /* key should have worked! */ + GNUNET_break (0); + return 6; + } + break; + default: + /* unexpected error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error %d\n", + ec); + return 7; + } + } + if (! success) + { + /* no valid key for signing found, also bad */ + GNUNET_break (0); + return 16; + } + + /* check signing does not work if the key is unknown */ + { + struct TALER_PlanchetDetail pd; + struct TALER_CsPubHashP rnd; + struct TALER_CRYPTO_CsSignRequest csr; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &rnd, + sizeof (rnd)); + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[0].denom_pub, + &alg_values, + &bks, + &nonce, + &coin_priv, + NULL, /* no age commitment */ + &c_hash, + &pd)); + csr.h_cs = &rnd; + csr.blinded_planchet + = &pd.blinded_planchet.blinded_message->details.cs_blinded_message; + ec = TALER_CRYPTO_helper_cs_sign ( + dh, + &csr, + false, + &ds); + TALER_blinded_planchet_free (&pd.blinded_planchet); + if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) + { + if (TALER_EC_NONE == ec) + TALER_blinded_denom_sig_free (&ds); + GNUNET_break (0); + return 17; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing with invalid key %s failed as desired\n", + GNUNET_h2s (&rnd.hash)); + } + return 0; +} + + +/** + * Test batch signing logic. + * + * @param dh handle to the helper + * @param batch_size how large should the batch be + * @param check_sigs also check unknown key and signatures + * @return 0 on success + */ +static int +test_batch_signing (struct TALER_CRYPTO_CsDenominationHelper *dh, + unsigned int batch_size, + bool check_sigs) +{ + struct TALER_BlindedDenominationSignature ds[batch_size]; + enum TALER_ErrorCode ec; + bool success = false; + struct TALER_PlanchetMasterSecretP ps[batch_size]; + struct TALER_CoinSpendPrivateKeyP coin_priv[batch_size]; + union GNUNET_CRYPTO_BlindingSecretP bks[batch_size]; + struct TALER_CoinPubHashP c_hash[batch_size]; + struct GNUNET_CRYPTO_BlindingInputValues bi[batch_size]; + struct TALER_ExchangeWithdrawValues alg_values[batch_size]; + union GNUNET_CRYPTO_BlindSessionNonce nonces[batch_size]; + + for (unsigned int i = 0; i<batch_size; i++) + TALER_planchet_master_setup_random (&ps[i]); + for (unsigned int k = 0; k<MAX_KEYS; k++) + { + if (! keys[k].valid) + continue; + { + struct TALER_PlanchetDetail pd[batch_size]; + struct TALER_CRYPTO_CsSignRequest csr[batch_size]; + struct TALER_CRYPTO_CsDeriveRequest cdr[batch_size]; + struct GNUNET_CRYPTO_CSPublicRPairP crps[batch_size]; + + for (unsigned int i = 0; i<batch_size; i++) + { + cdr[i].h_cs = &keys[k].h_cs; + cdr[i].nonce = &nonces[i].cs_nonce; + TALER_cs_withdraw_nonce_derive ( + &ps[i], + &nonces[i].cs_nonce); + bi[i].cipher = GNUNET_CRYPTO_BSA_CS; + alg_values[i].blinding_inputs = &bi[i]; + } + ec = TALER_CRYPTO_helper_cs_r_batch_derive ( + dh, + batch_size, + cdr, + false, + crps); + if (TALER_EC_NONE != ec) + continue; + for (unsigned int i = 0; i<batch_size; i++) + { + bi[i].details.cs_values = crps[i]; + TALER_planchet_setup_coin_priv (&ps[i], + &alg_values[i], + &coin_priv[i]); + TALER_planchet_blinding_secret_create (&ps[i], + &alg_values[i], + &bks[i]); + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[k].denom_pub, + &alg_values[i], + &bks[i], + &nonces[i], + &coin_priv[i], + NULL, /* no age commitment */ + &c_hash[i], + &pd[i])); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting signature with key %s\n", + GNUNET_h2s (&keys[k].h_cs.hash)); + csr[i].h_cs = &keys[k].h_cs; + csr[i].blinded_planchet + = &pd[i].blinded_planchet.blinded_message->details.cs_blinded_message; + } + ec = TALER_CRYPTO_helper_cs_batch_sign ( + dh, + batch_size, + csr, + false, + ds); + for (unsigned int i = 0; i<batch_size; i++) + { + TALER_blinded_planchet_free (&pd[i].blinded_planchet); + } + } + switch (ec) + { + case TALER_EC_NONE: + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[k].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) + { + /* key worked too early */ + GNUNET_break (0); + return 4; + } + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[k].start_time.abs_time), + >, + keys[k].validity_duration)) + { + /* key worked too later */ + GNUNET_break (0); + return 5; + } + if (check_sigs) + { + for (unsigned int i = 0; i<batch_size; i++) + { + struct TALER_FreshCoin coin; + + if (GNUNET_OK != + TALER_planchet_to_coin (&keys[k].denom_pub, + &ds[i], + &bks[i], + &coin_priv[i], + NULL, /* no age commitment */ + &c_hash[i], + &alg_values[i], + &coin)) + { + GNUNET_break (0); + return 6; + } + TALER_blinded_denom_sig_free (&ds[i]); + TALER_denom_sig_free (&coin.sig); + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received valid signature for key %s\n", + GNUNET_h2s (&keys[k].h_cs.hash)); + } + else + { + for (unsigned int i = 0; i<batch_size; i++) + TALER_blinded_denom_sig_free (&ds[i]); + } + success = true; + break; + case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: + /* This 'failure' is expected, we're testing also for the + error handling! */ + if ( (GNUNET_TIME_relative_is_zero ( + GNUNET_TIME_absolute_get_remaining ( + keys[k].start_time.abs_time))) && + (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + keys[k].start_time.abs_time), + <, + keys[k].validity_duration)) ) + { + /* key should have worked! */ + GNUNET_break (0); + return 6; + } + break; + default: + /* unexpected error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error %d\n", + ec); + return 7; + } + } + if (! success) + { + /* no valid key for signing found, also bad */ + GNUNET_break (0); + return 16; + } + + /* check signing does not work if the key is unknown */ + if (check_sigs) + { + struct TALER_PlanchetDetail pd; + struct TALER_CsPubHashP rnd; + struct TALER_CRYPTO_CsSignRequest csr; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &rnd, + sizeof (rnd)); + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[0].denom_pub, + &alg_values[0], + &bks[0], + &nonces[0], + &coin_priv[0], + NULL, /* no age commitment */ + &c_hash[0], + &pd)); + csr.h_cs = &rnd; + csr.blinded_planchet + = &pd.blinded_planchet.blinded_message->details.cs_blinded_message; + ec = TALER_CRYPTO_helper_cs_batch_sign ( + dh, + 1, + &csr, + false, + &ds[0]); + TALER_blinded_planchet_free (&pd.blinded_planchet); + if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) + { + if (TALER_EC_NONE == ec) + TALER_blinded_denom_sig_free (&ds[0]); + GNUNET_break (0); + return 17; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing with invalid key %s failed as desired\n", + GNUNET_h2s (&rnd.hash)); + } + return 0; +} + + +/** + * Benchmark signing logic. + * + * @param dh handle to the helper + * @return 0 on success + */ +static int +perf_signing (struct TALER_CRYPTO_CsDenominationHelper *dh, + const char *type) +{ + struct TALER_BlindedDenominationSignature ds; + enum TALER_ErrorCode ec; + struct GNUNET_TIME_Relative duration; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + struct GNUNET_CRYPTO_BlindingInputValues bv = { + .cipher = GNUNET_CRYPTO_BSA_CS + }; + struct TALER_ExchangeWithdrawValues alg_values = { + .blinding_inputs = &bv + }; + + TALER_planchet_master_setup_random (&ps); + duration = GNUNET_TIME_UNIT_ZERO; + TALER_CRYPTO_helper_cs_poll (dh); + for (unsigned int j = 0; j<NUM_SIGN_PERFS;) + { + for (unsigned int i = 0; i<MAX_KEYS; i++) + { + if (! keys[i].valid) + continue; + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) + continue; + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + >, + keys[i].validity_duration)) + continue; + { + struct TALER_CoinPubHashP c_hash; + struct TALER_PlanchetDetail pd; + union GNUNET_CRYPTO_BlindSessionNonce nonce; + struct TALER_CRYPTO_CsDeriveRequest cdr = { + .h_cs = &keys[i].h_cs, + .nonce = &nonce.cs_nonce + }; + + TALER_cs_withdraw_nonce_derive ( + &ps, + &nonce.cs_nonce); + ec = TALER_CRYPTO_helper_cs_r_derive ( + dh, + &cdr, + true, + &bv.details.cs_values); + if (TALER_EC_NONE != ec) + continue; + TALER_planchet_setup_coin_priv (&ps, + &alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + &alg_values, + &bks); + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[i].denom_pub, + &alg_values, + &bks, + &nonce, + &coin_priv, + NULL, /* no age commitment */ + &c_hash, + &pd)); + /* use this key as long as it works */ + while (1) + { + struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_Relative delay; + struct TALER_CRYPTO_CsSignRequest csr; + + csr.h_cs = &keys[i].h_cs; + csr.blinded_planchet + = &pd.blinded_planchet.blinded_message->details.cs_blinded_message; + ec = TALER_CRYPTO_helper_cs_sign ( + dh, + &csr, + true, + &ds); + if (TALER_EC_NONE != ec) + break; + delay = GNUNET_TIME_absolute_get_duration (start); + duration = GNUNET_TIME_relative_add (duration, + delay); + TALER_blinded_denom_sig_free (&ds); + j++; + if (NUM_SIGN_PERFS <= j) + break; + } + TALER_blinded_planchet_free (&pd.blinded_planchet); + } + } /* for i */ + } /* for j */ + fprintf (stderr, + "%u (%s) signature operations took %s\n", + (unsigned int) NUM_SIGN_PERFS, + type, + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_YES)); + return 0; +} + + +/** + * Parallel signing logic. + * + * @param esh handle to the helper + * @return 0 on success + */ +static int +par_signing (struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_TIME_Absolute start; + struct GNUNET_TIME_Relative duration; + pid_t pids[NUM_CORES]; + struct TALER_CRYPTO_CsDenominationHelper *dh; + + start = GNUNET_TIME_absolute_get (); + for (unsigned int i = 0; i<NUM_CORES; i++) + { + pids[i] = fork (); + num_keys = 0; + GNUNET_assert (-1 != pids[i]); + if (0 == pids[i]) + { + int ret; + + dh = TALER_CRYPTO_helper_cs_connect (cfg, + "taler-exchange", + &key_cb, + NULL); + GNUNET_assert (NULL != dh); + ret = perf_signing (dh, + "parallel"); + TALER_CRYPTO_helper_cs_disconnect (dh); + free_keys (); + exit (ret); + } + } + for (unsigned int i = 0; i<NUM_CORES; i++) + { + int wstatus; + + GNUNET_assert (pids[i] == + waitpid (pids[i], + &wstatus, + 0)); + } + duration = GNUNET_TIME_absolute_get_duration (start); + fprintf (stderr, + "%u (parallel) signature operations took %s (total real time)\n", + (unsigned int) NUM_SIGN_PERFS * NUM_CORES, + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_YES)); + return 0; +} + + +/** + * Main entry point into the test logic with the helper already running. + */ +static int +run_test (void) +{ + struct GNUNET_CONFIGURATION_Handle *cfg; + struct TALER_CRYPTO_CsDenominationHelper *dh; + struct timespec req = { + .tv_nsec = 250000000 + }; + int ret; + + cfg = GNUNET_CONFIGURATION_create (); + if (GNUNET_OK != + GNUNET_CONFIGURATION_load (cfg, + "test_helper_cs.conf")) + { + GNUNET_break (0); + return 77; + } + + fprintf (stderr, "Waiting for helper to start ... "); + for (unsigned int i = 0; i<100; i++) + { + nanosleep (&req, + NULL); + dh = TALER_CRYPTO_helper_cs_connect (cfg, + "taler-exchange", + &key_cb, + NULL); + if (NULL != dh) + break; + fprintf (stderr, "."); + } + if (NULL == dh) + { + fprintf (stderr, + "\nFAILED: timeout trying to connect to helper\n"); + GNUNET_CONFIGURATION_destroy (cfg); + return 1; + } + if (0 == num_keys) + { + fprintf (stderr, + "\nFAILED: timeout trying to connect to helper\n"); + TALER_CRYPTO_helper_cs_disconnect (dh); + GNUNET_CONFIGURATION_destroy (cfg); + return 1; + } + fprintf (stderr, + " Done (%u keys)\n", + num_keys); + ret = 0; + if (0 == ret) + ret = test_revocation (dh); + if (0 == ret) + ret = test_r_derive (dh); + if (0 == ret) + ret = test_signing (dh); + if (0 == ret) + ret = test_batch_signing (dh, + 2, + true); + if (0 == ret) + ret = test_batch_signing (dh, + 256, + true); + for (unsigned int i = 0; i<5; i++) + { + static unsigned int batches[] = { 1, 4, 16, 64, 256 }; + unsigned int batch_size = batches[i]; + struct GNUNET_TIME_Absolute start; + struct GNUNET_TIME_Relative duration; + + start = GNUNET_TIME_absolute_get (); + if (0 != ret) + break; + ret = test_batch_signing (dh, + batch_size, + false); + duration = GNUNET_TIME_absolute_get_duration (start); + fprintf (stderr, + "%4u (batch) signature operations took %s (total real time)\n", + (unsigned int) batch_size, + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_YES)); + } + if (0 == ret) + ret = perf_signing (dh, + "sequential"); + TALER_CRYPTO_helper_cs_disconnect (dh); + free_keys (); + if (0 == ret) + ret = par_signing (cfg); + /* clean up our state */ + GNUNET_CONFIGURATION_destroy (cfg); + return ret; +} + + +int +main (int argc, + const char *const argv[]) +{ + struct GNUNET_OS_Process *helper; + char *libexec_dir; + char *binary_name; + int ret; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + const char *loglev = "WARNING"; + + (void) argc; + (void) argv; + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); + GNUNET_log_setup ("test-helper-cs", + loglev, + NULL); + GNUNET_OS_init (TALER_project_data_default ()); + libexec_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_BINDIR); + GNUNET_asprintf (&binary_name, + "%s/%s", + libexec_dir, + "taler-exchange-secmod-cs"); + GNUNET_free (libexec_dir); + helper = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + NULL, NULL, NULL, + binary_name, + binary_name, + "-c", + "test_helper_cs.conf", + "-L", + loglev, + NULL); + if (NULL == helper) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "exec", + binary_name); + GNUNET_free (binary_name); + return 77; + } + GNUNET_free (binary_name); + ret = run_test (); + + GNUNET_OS_process_kill (helper, + SIGTERM); + if (GNUNET_OK != + GNUNET_OS_process_wait_status (helper, + &type, + &code)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Helper process did not die voluntarily, killing hard\n"); + GNUNET_OS_process_kill (helper, + SIGKILL); + ret = 4; + } + else if ( (GNUNET_OS_PROCESS_EXITED != type) || + (0 != code) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Helper died with unexpected status %d/%d\n", + (int) type, + (int) code); + ret = 5; + } + GNUNET_OS_process_destroy (helper); + return ret; +} + + +/* end of test_helper_cs.c */ diff --git a/src/util/test_helper_cs.conf b/src/util/test_helper_cs.conf new file mode 100644 index 000000000..f3b5b834c --- /dev/null +++ b/src/util/test_helper_cs.conf @@ -0,0 +1,11 @@ +[PATHS] +# Persistent data storage for the testcase +TALER_TEST_HOME = test_helper_cs_home/ + +[coin_1] +DURATION_WITHDRAW = 1 minute +CIPHER = CS + +[taler-exchange-secmod-cs] +LOOKAHEAD_SIGN = 5 minutes +OVERLAP_DURATION = 1 s diff --git a/src/util/test_helper_eddsa.c b/src/util/test_helper_eddsa.c index 471441c68..0119e4278 100644 --- a/src/util/test_helper_eddsa.c +++ b/src/util/test_helper_eddsa.c @@ -65,7 +65,7 @@ struct KeyData /** * Validity start point. */ - struct GNUNET_TIME_Absolute start_time; + struct GNUNET_TIME_Timestamp start_time; /** * Key expires for signing at @e start_time plus this value. @@ -112,7 +112,7 @@ static struct KeyData keys[MAX_KEYS]; */ static void key_cb (void *cls, - struct GNUNET_TIME_Absolute start_time, + struct GNUNET_TIME_Timestamp start_time, struct GNUNET_TIME_Relative validity_duration, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct TALER_SecurityModulePublicKeyP *sm_pub, @@ -207,7 +207,7 @@ test_revocation (struct TALER_CRYPTO_ExchangeSignHelper *esh) TALER_CRYPTO_helper_esign_poll (esh); if ( (! keys[j].revoked) || (GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_add (keys[j].start_time, + GNUNET_TIME_absolute_add (keys[j].start_time.abs_time, keys[j].validity_duration))) ) { break; @@ -217,7 +217,7 @@ test_revocation (struct TALER_CRYPTO_ExchangeSignHelper *esh) } if ( (keys[j].revoked) && (! GNUNET_TIME_absolute_is_past ( - GNUNET_TIME_absolute_add (keys[j].start_time, + GNUNET_TIME_absolute_add (keys[j].start_time.abs_time, keys[j].validity_duration))) ) { fprintf (stderr, @@ -365,6 +365,7 @@ par_signing (struct GNUNET_CONFIGURATION_Handle *cfg) int ret; esh = TALER_CRYPTO_helper_esign_connect (cfg, + "taler-exchange", &key_cb, NULL); if (NULL == esh) @@ -427,6 +428,7 @@ run_test (void) nanosleep (&req, NULL); esh = TALER_CRYPTO_helper_esign_connect (cfg, + "taler-exchange", &key_cb, NULL); if (NULL != esh) diff --git a/src/util/test_helper_eddsa.conf b/src/util/test_helper_eddsa.conf index 8fe119c40..a13833c02 100644 --- a/src/util/test_helper_eddsa.conf +++ b/src/util/test_helper_eddsa.conf @@ -1,7 +1,6 @@ [PATHS] # Persistent data storage for the testcase TALER_TEST_HOME = test_helper_eddsa_home/ -TALER_RUNTIME_DIR = ${TMPDIR:-/tmp}/${USER}/test_helper_eddsa/ [taler-exchange-secmod-eddsa] CLIENT_DIR = $TALER_RUNTIME_DIR diff --git a/src/util/test_helper_rsa.c b/src/util/test_helper_rsa.c index 14ff2bfab..2bc15879f 100644 --- a/src/util/test_helper_rsa.c +++ b/src/util/test_helper_rsa.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2020, 2021 Taler Systems SA + (C) 2020, 2021, 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 @@ -64,7 +64,7 @@ struct KeyData /** * Validity start point. */ - struct GNUNET_TIME_Absolute start_time; + struct GNUNET_TIME_Timestamp start_time; /** * Key expires for signing at @e start_time plus this value. @@ -129,7 +129,7 @@ free_keys (void) * @param validity_duration how long does the key remain available for signing; * zero if the key has been revoked or purged * @param h_rsa hash of the @a denom_pub that is available (or was purged) - * @param denom_pub the public key itself, NULL if the key was revoked or purged + * @param bs_pub the public key itself, NULL if the key was revoked or purged * @param sm_pub public key of the security module, NULL if the key was revoked or purged * @param sm_sig signature from the security module, NULL if the key was revoked or purged * The signature was already verified against @a sm_pub. @@ -137,10 +137,10 @@ free_keys (void) static void key_cb (void *cls, const char *section_name, - struct GNUNET_TIME_Absolute start_time, + struct GNUNET_TIME_Timestamp start_time, struct GNUNET_TIME_Relative validity_duration, const struct TALER_RsaPubHashP *h_rsa, - const struct TALER_DenominationPublicKey *denom_pub, + struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub, const struct TALER_SecurityModulePublicKeyP *sm_pub, const struct TALER_SecurityModuleSignatureP *sm_sig) { @@ -155,7 +155,7 @@ key_cb (void *cls, { bool found = false; - GNUNET_break (NULL == denom_pub); + GNUNET_break (NULL == bs_pub); GNUNET_break (NULL == section_name); for (unsigned int i = 0; i<MAX_KEYS; i++) if (0 == GNUNET_memcmp (h_rsa, @@ -176,7 +176,7 @@ key_cb (void *cls, return; } - GNUNET_break (NULL != denom_pub); + GNUNET_break (NULL != bs_pub); for (unsigned int i = 0; i<MAX_KEYS; i++) if (! keys[i].valid) { @@ -184,8 +184,8 @@ key_cb (void *cls, keys[i].h_rsa = *h_rsa; keys[i].start_time = start_time; keys[i].validity_duration = validity_duration; - TALER_denom_pub_deep_copy (&keys[i].denom_pub, - denom_pub); + keys[i].denom_pub.bsign_pub_key + = GNUNET_CRYPTO_bsign_pub_incref (bs_pub); num_keys++; return; } @@ -267,45 +267,83 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) struct TALER_BlindedDenominationSignature ds; enum TALER_ErrorCode ec; bool success = false; - struct TALER_PlanchetSecretsP ps; - struct TALER_CoinPubHash c_hash; + struct TALER_PlanchetMasterSecretP ps; + const struct TALER_ExchangeWithdrawValues *alg_values + = TALER_denom_ewv_rsa_singleton (); + struct TALER_AgeCommitmentHash ach; + struct TALER_CoinPubHashP c_hash; + struct TALER_CoinSpendPrivateKeyP coin_priv; + union GNUNET_CRYPTO_BlindingSecretP bks; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &ps, + sizeof (ps)); + TALER_planchet_setup_coin_priv (&ps, + alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + alg_values, + &bks); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &ach, + sizeof(ach)); - TALER_planchet_setup_random (&ps); for (unsigned int i = 0; i<MAX_KEYS; i++) { if (! keys[i].valid) continue; + if (GNUNET_CRYPTO_BSA_RSA != + keys[i].denom_pub.bsign_pub_key->cipher) + continue; { struct TALER_PlanchetDetail pd; GNUNET_assert (GNUNET_YES == TALER_planchet_prepare (&keys[i].denom_pub, - &ps, + alg_values, + &bks, + NULL, + &coin_priv, + &ach, &c_hash, &pd)); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Requesting signature over %u bytes with key %s\n", - (unsigned int) pd.coin_ev_size, - GNUNET_h2s (&keys[i].h_rsa.hash)); - ds = TALER_CRYPTO_helper_rsa_sign (dh, - &keys[i].h_rsa, - pd.coin_ev, - pd.coin_ev_size, - &ec); - GNUNET_free (pd.coin_ev); + { + struct TALER_CRYPTO_RsaSignRequest rsr = { + .h_rsa = &keys[i].h_rsa, + .msg = + pd.blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg, + .msg_size = + pd.blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg_size + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Requesting signature over %u bytes with key %s\n", + (unsigned int) rsr.msg_size, + GNUNET_h2s (&rsr.h_rsa->hash)); + ec = TALER_CRYPTO_helper_rsa_sign (dh, + &rsr, + &ds); + } + TALER_blinded_planchet_free (&pd.blinded_planchet); } switch (ec) { case TALER_EC_NONE: - if (GNUNET_TIME_absolute_get_remaining (keys[i].start_time).rel_value_us > - GNUNET_TIME_UNIT_SECONDS.rel_value_us) + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) { /* key worked too early */ GNUNET_break (0); return 4; } - if (GNUNET_TIME_absolute_get_duration (keys[i].start_time).rel_value_us > - keys[i].validity_duration.rel_value_us) + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + >, + keys[i].validity_duration)) { /* key worked too later */ GNUNET_break (0); @@ -317,7 +355,9 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) if (GNUNET_OK != TALER_denom_sig_unblind (&rs, &ds, - &ps.blinding_key, + &bks, + &c_hash, + alg_values, &keys[i].denom_pub)) { GNUNET_break (0); @@ -344,12 +384,14 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: /* This 'failure' is expected, we're testing also for the error handling! */ - if ( (0 == - GNUNET_TIME_absolute_get_remaining ( - keys[i].start_time).rel_value_us) && - (GNUNET_TIME_absolute_get_duration ( - keys[i].start_time).rel_value_us < - keys[i].validity_duration.rel_value_us) ) + if ( (GNUNET_TIME_relative_is_zero ( + GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time))) && + (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + <, + keys[i].validity_duration)) ) { /* key should have worked! */ GNUNET_break (0); @@ -359,8 +401,10 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) default: /* unexpected error */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected error %d\n", - ec); + "Unexpected error %d at %s:%u\n", + ec, + __FILE__, + __LINE__); return 7; } } @@ -374,15 +418,18 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) /* check signing does not work if the key is unknown */ { struct TALER_RsaPubHashP rnd; + struct TALER_CRYPTO_RsaSignRequest rsr = { + .h_rsa = &rnd, + .msg = "Hello", + .msg_size = strlen ("Hello") + }; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &rnd, sizeof (rnd)); - ds = TALER_CRYPTO_helper_rsa_sign (dh, - &rnd, - "Hello", - strlen ("Hello"), - &ec); + ec = TALER_CRYPTO_helper_rsa_sign (dh, + &rsr, + &ds); if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) { if (TALER_EC_NONE == ec) @@ -399,6 +446,227 @@ test_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh) /** + * Test batch signing logic. + * + * @param dh handle to the helper + * @param batch_size how large should the batch be + * @param check_sigs also check unknown key and signatures + * @return 0 on success + */ +static int +test_batch_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, + unsigned int batch_size, + bool check_sigs) +{ + struct TALER_BlindedDenominationSignature ds[batch_size]; + enum TALER_ErrorCode ec; + bool success = false; + struct TALER_PlanchetMasterSecretP ps[batch_size]; + const struct TALER_ExchangeWithdrawValues *alg_values; + struct TALER_AgeCommitmentHash ach[batch_size]; + struct TALER_CoinPubHashP c_hash[batch_size]; + struct TALER_CoinSpendPrivateKeyP coin_priv[batch_size]; + union GNUNET_CRYPTO_BlindingSecretP bks[batch_size]; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, + &ps, + sizeof (ps)); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &ach, + sizeof(ach)); + alg_values = TALER_denom_ewv_rsa_singleton (); + for (unsigned int i = 0; i<batch_size; i++) + { + TALER_planchet_setup_coin_priv (&ps[i], + alg_values, + &coin_priv[i]); + TALER_planchet_blinding_secret_create (&ps[i], + alg_values, + &bks[i]); + } + for (unsigned int k = 0; k<MAX_KEYS; k++) + { + if (success && ! check_sigs) + break; /* only do one round */ + if (! keys[k].valid) + continue; + if (GNUNET_CRYPTO_BSA_RSA != + keys[k].denom_pub.bsign_pub_key->cipher) + continue; + { + struct TALER_PlanchetDetail pd[batch_size]; + struct TALER_CRYPTO_RsaSignRequest rsr[batch_size]; + + for (unsigned int i = 0; i<batch_size; i++) + { + GNUNET_assert (GNUNET_YES == + TALER_planchet_prepare (&keys[k].denom_pub, + alg_values, + &bks[i], + NULL, + &coin_priv[i], + &ach[i], + &c_hash[i], + &pd[i])); + rsr[i].h_rsa + = &keys[k].h_rsa; + rsr[i].msg + = pd[i].blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg; + rsr[i].msg_size + = pd[i].blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg_size; + } + ec = TALER_CRYPTO_helper_rsa_batch_sign (dh, + batch_size, + rsr, + ds); + for (unsigned int i = 0; i<batch_size; i++) + { + if (TALER_EC_NONE == ec) + GNUNET_break (GNUNET_CRYPTO_BSA_RSA == + ds[i].blinded_sig->cipher); + TALER_blinded_planchet_free (&pd[i].blinded_planchet); + } + } + switch (ec) + { + case TALER_EC_NONE: + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[k].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) + { + /* key worked too early */ + GNUNET_break (0); + return 4; + } + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[k].start_time.abs_time), + >, + keys[k].validity_duration)) + { + /* key worked too later */ + GNUNET_break (0); + return 5; + } + for (unsigned int i = 0; i<batch_size; i++) + { + struct TALER_DenominationSignature rs; + + if (check_sigs) + { + if (GNUNET_OK != + TALER_denom_sig_unblind (&rs, + &ds[i], + &bks[i], + &c_hash[i], + alg_values, + &keys[k].denom_pub)) + { + GNUNET_break (0); + return 6; + } + } + TALER_blinded_denom_sig_free (&ds[i]); + if (check_sigs) + { + if (GNUNET_OK != + TALER_denom_pub_verify (&keys[k].denom_pub, + &rs, + &c_hash[i])) + { + /* signature invalid */ + GNUNET_break (0); + TALER_denom_sig_free (&rs); + return 7; + } + TALER_denom_sig_free (&rs); + } + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received valid signature for key %s\n", + GNUNET_h2s (&keys[k].h_rsa.hash)); + success = true; + break; + case TALER_EC_EXCHANGE_DENOMINATION_HELPER_TOO_EARLY: + /* This 'failure' is expected, we're testing also for the + error handling! */ + for (unsigned int i = 0; i<batch_size; i++) + TALER_blinded_denom_sig_free (&ds[i]); + if ( (GNUNET_TIME_relative_is_zero ( + GNUNET_TIME_absolute_get_remaining ( + keys[k].start_time.abs_time))) && + (GNUNET_TIME_relative_cmp ( + GNUNET_TIME_absolute_get_duration ( + keys[k].start_time.abs_time), + <, + keys[k].validity_duration)) ) + { + /* key should have worked! */ + GNUNET_break (0); + return 6; + } + break; + case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN: + for (unsigned int i = 0; i<batch_size; i++) + TALER_blinded_denom_sig_free (&ds[i]); + break; + default: + /* unexpected error */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected error %d at %s:%u\n", + ec, + __FILE__, + __LINE__); + for (unsigned int i = 0; i<batch_size; i++) + TALER_blinded_denom_sig_free (&ds[i]); + return 7; + } + } + if (! success) + { + /* no valid key for signing found, also bad */ + GNUNET_break (0); + return 16; + } + + /* check signing does not work if the key is unknown */ + if (check_sigs) + { + struct TALER_RsaPubHashP rnd; + struct TALER_CRYPTO_RsaSignRequest rsr = { + .h_rsa = &rnd, + .msg = "Hello", + .msg_size = strlen ("Hello") + }; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &rnd, + sizeof (rnd)); + ec = TALER_CRYPTO_helper_rsa_batch_sign (dh, + 1, + &rsr, + ds); + if (TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN != ec) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Signing with invalid key returned unexpected status %d\n", + ec); + if (TALER_EC_NONE == ec) + TALER_blinded_denom_sig_free (ds); + GNUNET_break (0); + return 17; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Signing with invalid key %s failed as desired\n", + GNUNET_h2s (&rnd.hash)); + } + return 0; +} + + +/** * Benchmark signing logic. * * @param dh handle to the helper @@ -411,9 +679,23 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, struct TALER_BlindedDenominationSignature ds; enum TALER_ErrorCode ec; struct GNUNET_TIME_Relative duration; - struct TALER_PlanchetSecretsP ps; - - TALER_planchet_setup_random (&ps); + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPrivateKeyP coin_priv; + struct TALER_AgeCommitmentHash ach; + union GNUNET_CRYPTO_BlindingSecretP bks; + const struct TALER_ExchangeWithdrawValues *alg_values + = TALER_denom_ewv_rsa_singleton (); + + TALER_planchet_master_setup_random (&ps); + TALER_planchet_setup_coin_priv (&ps, + alg_values, + &coin_priv); + TALER_planchet_blinding_secret_create (&ps, + alg_values, + &bks); + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &ach, + sizeof(ach)); duration = GNUNET_TIME_UNIT_ZERO; TALER_CRYPTO_helper_rsa_poll (dh); for (unsigned int j = 0; j<NUM_SIGN_PERFS;) @@ -422,19 +704,30 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, { if (! keys[i].valid) continue; - if (GNUNET_TIME_absolute_get_remaining (keys[i].start_time).rel_value_us > - GNUNET_TIME_UNIT_SECONDS.rel_value_us) + if (GNUNET_CRYPTO_BSA_RSA != + keys[i].denom_pub.bsign_pub_key->cipher) + continue; + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_remaining ( + keys[i].start_time.abs_time), + >, + GNUNET_TIME_UNIT_SECONDS)) continue; - if (GNUNET_TIME_absolute_get_duration (keys[i].start_time).rel_value_us > - keys[i].validity_duration.rel_value_us) + if (GNUNET_TIME_relative_cmp (GNUNET_TIME_absolute_get_duration ( + keys[i].start_time.abs_time), + >, + keys[i].validity_duration)) continue; { - struct TALER_CoinPubHash c_hash; + struct TALER_CoinPubHashP c_hash; struct TALER_PlanchetDetail pd; GNUNET_assert (GNUNET_YES == TALER_planchet_prepare (&keys[i].denom_pub, - &ps, + alg_values, + &bks, + NULL, + &coin_priv, + &ach, &c_hash, &pd)); /* use this key as long as it works */ @@ -442,12 +735,19 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, { struct GNUNET_TIME_Absolute start = GNUNET_TIME_absolute_get (); struct GNUNET_TIME_Relative delay; - - ds = TALER_CRYPTO_helper_rsa_sign (dh, - &keys[i].h_rsa, - pd.coin_ev, - pd.coin_ev_size, - &ec); + struct TALER_CRYPTO_RsaSignRequest rsr = { + .h_rsa = &keys[i].h_rsa, + .msg = + pd.blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg, + .msg_size = + pd.blinded_planchet.blinded_message->details.rsa_blinded_message. + blinded_msg_size + }; + + ec = TALER_CRYPTO_helper_rsa_sign (dh, + &rsr, + &ds); if (TALER_EC_NONE != ec) break; delay = GNUNET_TIME_absolute_get_duration (start); @@ -458,7 +758,7 @@ perf_signing (struct TALER_CRYPTO_RsaDenominationHelper *dh, if (NUM_SIGN_PERFS <= j) break; } - GNUNET_free (pd.coin_ev); + TALER_blinded_planchet_free (&pd.blinded_planchet); } } /* for i */ } /* for j */ @@ -497,6 +797,7 @@ par_signing (struct GNUNET_CONFIGURATION_Handle *cfg) int ret; dh = TALER_CRYPTO_helper_rsa_connect (cfg, + "taler-exchange", &key_cb, NULL); GNUNET_assert (NULL != dh); @@ -548,12 +849,14 @@ run_test (void) return 77; } - fprintf (stderr, "Waiting for helper to start ... "); + fprintf (stderr, + "Waiting for helper to start ... "); for (unsigned int i = 0; i<100; i++) { nanosleep (&req, NULL); dh = TALER_CRYPTO_helper_rsa_connect (cfg, + "taler-exchange", &key_cb, NULL); if (NULL != dh) @@ -584,6 +887,34 @@ run_test (void) if (0 == ret) ret = test_signing (dh); if (0 == ret) + ret = test_batch_signing (dh, + 2, + true); + if (0 == ret) + ret = test_batch_signing (dh, + 256, + true); + for (unsigned int i = 0; i<5; i++) + { + static unsigned int batches[] = { 1, 4, 16, 64, 256 }; + unsigned int batch_size = batches[i]; + struct GNUNET_TIME_Absolute start; + struct GNUNET_TIME_Relative duration; + + start = GNUNET_TIME_absolute_get (); + if (0 != ret) + break; + ret = test_batch_signing (dh, + batch_size, + false); + duration = GNUNET_TIME_absolute_get_duration (start); + fprintf (stderr, + "%4u (batch) signature operations took %s (total real time)\n", + (unsigned int) batch_size, + GNUNET_STRINGS_relative_time_to_string (duration, + GNUNET_YES)); + } + if (0 == ret) ret = perf_signing (dh, "sequential"); TALER_CRYPTO_helper_rsa_disconnect (dh); @@ -609,6 +940,8 @@ main (int argc, (void) argc; (void) argv; + unsetenv ("XDG_DATA_HOME"); + unsetenv ("XDG_CONFIG_HOME"); GNUNET_log_setup ("test-helper-rsa", "WARNING", NULL); diff --git a/src/util/test_helper_rsa.conf b/src/util/test_helper_rsa.conf index 66127ee01..d50e64d95 100644 --- a/src/util/test_helper_rsa.conf +++ b/src/util/test_helper_rsa.conf @@ -1,11 +1,10 @@ [PATHS] # Persistent data storage for the testcase TALER_TEST_HOME = test_helper_rsa_home/ -TALER_RUNTIME_DIR = ${TMPDIR:-/tmp}/${USER}/test_helper_rsa/ - [coin_1] DURATION_WITHDRAW = 1 minute +CIPHER = RSA RSA_KEYSIZE = 2048 [taler-exchange-secmod-rsa] diff --git a/src/util/test_payto.c b/src/util/test_payto.c index 4dc73a964..62ba7d28e 100644 --- a/src/util/test_payto.c +++ b/src/util/test_payto.c @@ -22,16 +22,16 @@ #include "taler_util.h" #define CHECK(a,b) do { \ - GNUNET_assert (a != NULL); \ - GNUNET_assert (b != NULL); \ - if (0 != strcmp (a,b)) { \ - GNUNET_break (0); \ - fprintf (stderr, "Got %s, wanted %s\n", b, a); \ - GNUNET_free (b); \ - return 1; \ - } else { \ - GNUNET_free (b); \ - } \ + GNUNET_assert (a != NULL); \ + GNUNET_assert (b != NULL); \ + if (0 != strcmp (a,b)) { \ + GNUNET_break (0); \ + fprintf (stderr, "Got %s, wanted %s\n", b, a); \ + GNUNET_free (b); \ + return 1; \ + } else { \ + GNUNET_free (b); \ + } \ } while (0) @@ -50,11 +50,55 @@ main (int argc, TALER_iban_validate ("FR1420041010050500013M02606")); GNUNET_assert (NULL == TALER_iban_validate ("DE89370400440532013000")); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/username?receiver-name=foo"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/~path/username?receiver-name=foo"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/~path/username?receiver-name=fo/o"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname/path/username?receiver-name=foo"); + GNUNET_assert (NULL == r); + r = TALER_payto_validate ( + "payto://x-taler-bank/https://hostname/username?receiver-name=foo"); + GNUNET_assert (NULL != r); + GNUNET_free (r); + r = TALER_payto_validate ( + "payto://x-taler-bank/hostname:4a2/path/username?receiver-name=foo"); + GNUNET_assert (NULL != r); + GNUNET_free (r); + r = TALER_payto_validate ( + "payto://x-taler-bank/-hostname/username?receiver-name=foo"); + GNUNET_assert (NULL != r); + GNUNET_free (r); + r = TALER_payto_validate ( + "payto://x-taler-bank/domain..name/username?receiver-name=foo"); + GNUNET_assert (NULL != r); + GNUNET_free (r); + r = TALER_payto_validate ( + "payto://x-taler-bank/domain..name/?receiver-name=foo"); + GNUNET_assert (NULL != r); + GNUNET_free (r); + r = TALER_payto_validate ( + "payto://x-taler-bank/domain.name/username"); + GNUNET_assert (NULL != r); + GNUNET_free (r); r = TALER_xtalerbank_account_from_payto ( "payto://x-taler-bank/localhost:1080/alice"); CHECK ("alice", r); r = TALER_xtalerbank_account_from_payto ( + "payto://x-taler-bank/localhost:1080/path/alice"); + CHECK ("alice", + r); + r = TALER_xtalerbank_account_from_payto ( + "payto://x-taler-bank/localhost:1080/path/alice?receiver-name=ali/cia"); + CHECK ("alice", + r); + r = TALER_xtalerbank_account_from_payto ( "payto://x-taler-bank/localhost:1080/alice?subject=hello&amount=EUR:1"); CHECK ("alice", r); diff --git a/src/util/tv_age_restriction.c b/src/util/tv_age_restriction.c new file mode 100644 index 000000000..9fc2b4823 --- /dev/null +++ b/src/util/tv_age_restriction.c @@ -0,0 +1,271 @@ +/** + * @file util/tv_age_restriction.c + * @brief Generate test vectors for age restriction + * @author Özgür Kesim + * + * compile in exchange/src/util with + * + * gcc tv_age_restriction.c \ + * -lgnunetutil -lgnunetjson -lsodium -ljansson \ + * -L/usr/lib/x86_64-linux-gnu -lmicrohttpd -ltalerutil \ + * -I../include \ + * -o tv_age_restriction + * + */ +#include "platform.h" +#include <gnunet/gnunet_json_lib.h> +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <taler/taler_util.h> +#include <taler/taler_crypto_lib.h> + +static struct TALER_AgeMask age_masks[] = { + { .bits = 1 + | 1 << 8 | 1 << 14 | 1 << 18 }, + { .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); + +/** + * Encodes the age mask into a string, like "8:10:12:14:16:18:21" + */ +char * +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; +} + + +static json_t * +cp_to_j ( + const struct GNUNET_HashCode *seed, + struct TALER_AgeCommitmentProof *acp, + uint8_t seq) +{ + json_t *j_commitment; + json_t *j_proof; + json_t *j_pubs; + json_t *j_privs; + struct TALER_AgeCommitmentHash hac = {0}; + char buf[256] = {0}; + + TALER_age_commitment_hash (&acp->commitment, &hac); + + j_pubs = json_array (); + GNUNET_assert (NULL != j_pubs); + for (unsigned int i = 0; i < acp->commitment.num; i++) + { + json_t *j_pub = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto (NULL, + &acp->commitment.keys[i])); + json_array_append_new (j_pubs, j_pub); + } + + j_commitment = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("num", acp->commitment.num), + GNUNET_JSON_pack_array_steal ("edx25519_pubs", j_pubs), + GNUNET_JSON_pack_data_auto ("h_age_commitment", &hac)); + + + j_privs = json_array (); + GNUNET_assert (NULL != j_privs); + for (unsigned int i = 0; i < acp->proof.num; i++) + { + json_t *j_priv = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto (NULL, + &acp->proof.keys[i])); + json_array_append_new (j_privs, j_priv); + } + j_proof = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_uint64 ("num", acp->proof.num), + GNUNET_JSON_pack_array_steal ("edx25519_privs", j_privs)); + + if (0 == seq) + { + strcpy (buf, "commit()"); + } + else + { + sprintf (buf, + "derive_from(%d)", + seq); + } + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("generated_by", buf), + GNUNET_JSON_pack_data_auto ("seed", seed), + GNUNET_JSON_pack_object_steal ("proof", j_proof), + GNUNET_JSON_pack_object_steal ("commitment", j_commitment)); + +}; + +static json_t * +generate ( + struct TALER_AgeMask *mask) +{ + uint8_t age; + json_t *j_commitproofs; + j_commitproofs = json_array (); + + for (age = 0; age < 24; age += 2) + { + json_t *j_top = json_object (); + json_t *j_seq = json_array (); + enum GNUNET_GenericReturnValue ret; + struct TALER_AgeCommitmentProof acp[3] = {0}; + uint8_t age_group = get_age_group (mask, age); + struct GNUNET_HashCode seed; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + + json_object_set (j_top, + "committed_age", + json_integer (age)); + + ret = TALER_age_restriction_commit (mask, + age, + &seed, + &acp[0]); + + GNUNET_assert (GNUNET_OK == ret); + + /* Also derive two more commitments right away */ + for (uint8_t i = 0; i<2; i++) + { + struct GNUNET_HashCode salt; + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &salt, + sizeof (salt)); + GNUNET_assert (GNUNET_OK == + TALER_age_commitment_derive (&acp[i], + &salt, + &acp[i + 1])); + } + + for (uint8_t i = 0; i < 3; i++) + { + json_t *j_cp = cp_to_j (&seed, &acp[i], i); + json_t *j_attestations = json_array (); + + for (uint8_t min = 0; min < 22; min++) + { + json_t *j_attest = json_object (); + json_t *j_reason; + uint8_t min_group = get_age_group (mask, min); + struct TALER_AgeAttestation at = {0}; + + json_object_set (j_attest, + "required_minimum_age", + json_integer (min)); + json_object_set (j_attest, + "calculated_age_group", + json_integer (min_group)); + + ret = TALER_age_commitment_attest (&acp[i], + min, + &at); + + + if (0 == min_group) + j_reason = json_string ( + "not required: age group is 0"); + else if (min_group > age_group) + j_reason = json_string ( + "not applicable: committed age too small"); + else + j_reason = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_data_auto (NULL, &at)); + + json_object_set (j_attest, + "attestation", + j_reason); + + json_array_append_new (j_attestations, + j_attest); + + } + + json_object_set (j_cp, "attestations", j_attestations); + json_array_append (j_seq, j_cp); + + TALER_age_commitment_proof_free (&acp[i]); + } + + json_object_set (j_top, "commitment_proof_attestation_seq", j_seq); + json_array_append_new (j_commitproofs, j_top); + } + + return j_commitproofs; +} + + +int +main (int argc, + const char *const argv[]) +{ + (void) argc; + (void) argv; + json_t *j_data = json_array (); + for (unsigned int i = 0; i < 2; i++) + { + struct TALER_AgeMask mask = age_masks[i]; + json_t *j_test = json_object (); + json_object_set (j_test, + "age_groups", + json_string (age_mask_to_string (&mask))); + json_object_set (j_test, + "age_mask", + json_integer (mask.bits)); + json_object_set (j_test, + "test_data", + generate (&mask)); + json_array_append_new (j_data, j_test); + } + printf ("%s\n", json_dumps (j_data, JSON_INDENT (2) + | JSON_COMPACT)); + + json_decref (j_data); + return 0; +} + + +/* end of tv_age_restriction.c */ diff --git a/src/util/tv_age_restriction.json b/src/util/tv_age_restriction.json new file mode 100644 index 000000000..e0c9cfc44 --- /dev/null +++ b/src/util/tv_age_restriction.json @@ -0,0 +1,9764 @@ +[ + { + "age_groups":"8:14:18", + "age_mask":278785, + "test_data":[ + { + "commited_age":0, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "HAMP2FPG381SY4E9E14R0SYG3AYMVHPVJ3VPBENJNZ66GC7GF7TG", + "915FEJ4C3R4FBFRDQP7E0BVEN52V17Q68SHHMCK2BN1KR3XA7SE0", + "Y568AFKGFD7MFBEAN96NCTAFRNJJJ84PX2J5VB837MRMNRDGWRV0" + ], + "h_age_commitment":"Z5KYVFENM1HV93MDG90Q6XXMAFMZSNNVTABG170VMY9J7Q2832RG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "P9MRXBN21AW7CSFPA03RY4RDD8FCY88ZXG0YENGWN0HEW17Q6X20", + "J19MKEY2P1CFAM7QMM9D53FTVVHC7PD17S5QWMXGE2MDJGXN7S8G", + "9DJTDHTVDQAD7HHTRAAVZHNMK5P8FQTEQHK1VX1DM5MJY4VS22HG" + ], + "h_age_commitment":"GW7QDAFFMJTVS5ZV9G8K4RARME3GS5CH9W6Z56QZTT1AVEBBS62G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"5SX8V28APB16XW6YJKNQAS7W6254C8MSCEA0YGEZR7CAM5N9KXPPJERKK6XGFEC21C21568VY1AYHWRS1G41GB9X520D9XZ85AHRRP0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "WX2W90VRGY6DA1JPA50HWJ9K7QJNAWG2ZN4EW93PFH09KZSNBTC0", + "5K2KHCTCMH33Z1CD8TP4JWY653HS1018SPZ00KAP7CK99XFKEGF0", + "08BHPJRAMB0ZWECJJGD422A0F04R9MC6RQRCWABVFM44F0NDBE6G" + ], + "h_age_commitment":"RZ2JVFN263HCMWCD24GP6KW9WX9W3M7TAP20VGH90ZT3F7E37GV0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":2, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "BDKGKAP7XYVS6VEY661BD5D29HYXXPVNA6EVSHN5FDK6CCCCBH0G", + "0ZJS7N0D91AY1HVP0B41X1C7PGJ66KDPQCE6D9WFCKKE8W1Q9YTG", + "QMFSVE4G3JE1DCS4X305W8R89YVSG0YSRKS18BB1RZ3TZX8TGFSG" + ], + "h_age_commitment":"ZEGNWEVQV6MWAN9HJRX7MN59ZFBZ14WK2M87ZJZ537QC7HD0Z4Q0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "83K5YRKK1YB8MV2SG43W1152RQA0X06Y01QEGC84AXQF0D6H1H8G", + "5A55B3B64E6NX7Q7F0RRPAE1XVVXX6FKKZ29SPKSZ8F74F0WB6P0", + "F2W8Y8PXEEPS4392112S2NS7SA8976Z88TRHS6MYW1MGGPD355E0" + ], + "h_age_commitment":"YY3YFQPJT28QE4Y7RJ5R0V2WKR6A4AMMR6WF5BBKB0704BFP12T0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"G38XFGPXHJQVNA326P41RRMB5JDCJM42DMNSJ6CTPKJHCJGVN2QPMRNQ1F365JJAV9SX6GYCEB4JXG45VCAX1ZGDCETGXY1FHS1SPZG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "DMR4CZ34HJCWFSK4GC8QRKF6ZSAT3DSCH8T527729HRESBHA57KG", + "W814ZAKCH5W3SDCGPP0T4DVVSAKD3XQ6J5DXNPA01KSGR1J2ZTP0", + "HEBV9DC4HRC7MMSBJ46WWX6DAGVFQAQPMQM9FNEHET5RKATGV9G0" + ], + "h_age_commitment":"GSC52EJ6M6JZ1ZF98Y46B5E9FTK6W6DDDFAZHKNE00D0YCB6J8NG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":4, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "KMWRF8YT11TBJFWFY1VD69QMB05H6BGCQ2DNBWW71EDZBN5E0GB0", + "0CBYDC8EV13WN5KDCYBZBC8KE1WZNF4H249JPYZCT314252VD800", + "8Q0SKZZN69SZF08GJ13XH5EHSKKYF1HWDZC9ZSJSTHG9TYEDQJMG" + ], + "h_age_commitment":"100D0J1QBVTK6WQAJPBCG2VPECKD1D91Q75CNCHNTZ4YRQ02WTE0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "9MQX7TD4WX28H87B958P83D02T0HB0S4XPV72Z4TXT9RG53QS3F0", + "RDVVD4ACSBMX4X44NG19V4E32MG95BHQAJSGM4C08G1KCGFRT44G", + "D7TENF4V347FETDKG2VRKK6Z92VPGGWEXECT4A6PCXG6DYTKA1Z0" + ], + "h_age_commitment":"11FXVH30Z3AJ7WSNF387VT6Y9GCD61N3T13ACDKAXDP4XFAQHA6G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"1SDTX459ENBEJYKWGPCPWSV10FB3Y10FZHP5W7J93F8GX0FGEQKT9T5W6TW19TZ9QXJHP88M4PZNJDFWFN5J07DA61EN0C9KP9TFMCR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "NEPX42NXKBBFYKDC3R2WND1N1FJN3M3RPRH5D19JKNSMCG4SABAG", + "FN6G98WGS312TH9QD9HNFXDXPKEWRW5YJ2S6YR4XSXFYF9SY3V10", + "Q7NPAT6SRTFMAWA3J5AT2JBC88K4VGYSYK45DEDF10VWGQ0YSMJG" + ], + "h_age_commitment":"T609RE4JCYNRNXWXYTW2M2Z56B3C4NDW7J4FCV1DHEWTQPNS6AW0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":6, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "H7AV678Q43GCBGYP5KX4JFYXR1T6DQBCJZ8DG709Y51Q8074BM5G", + "NCH1N3XP7AEZZ1AGY1RKWPECK7TDDZEH10F1VCDSWE629KTRRMP0", + "9906YSKNNQ6MXQX04RX2RASVWF08093A307G53VCRW08RQGJVPPG" + ], + "h_age_commitment":"S9KTKGPFK7NFZ44QYH0SVTD5478W8TCGDF7FBZK95JZMMND6JF00" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "H31EPGVQ0TDPFT13983S3HK3RQ9RX3FV1XNJWMB11A8KZEPERHYG", + "1RS6XJ7043KXW6NT4KS5ZCXEQVA3E7PWJDEWAGMGKK947DRSZM60", + "QH92P2QBJ80C7EJ19F8RH0SJHWWQCB01027SE72Z73NF1AWZCZY0" + ], + "h_age_commitment":"7SKSRBK1DNYM5SG9T3JE7C0S06DVBCVJNYWEHW7XE0PCFGRFWW7G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"JVB77H5KTXH6ZEBAMT0HT0EVHKGNPP3B63DXN4H7D39YE5Q0X7RTYZKAF9RA00HEA3JQ4F5CK1R7G4F9DR2RAPHY6K9216YE98KQ1WG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "DA7F7N1ZWA76ZBV5AV2MESJHFJB37VTQZF5JPQT2V38MBKXSGWH0", + "BTBF14ND21MGWJCNHVXN3YS1P7N0QDD273RES9N2R5ZMNRZC3V50", + "9R1GS0AYTHDMQPAWT5BTS3MBMXDCNCDDG8GWF6QR7FHJ6F94GJ60" + ], + "h_age_commitment":"TW2ZRZH19T22ACXQBD0RQY3BPXT7PKWX4W7MQ078JZZ53AYSY3TG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":8, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR", + "proof":{ + "num":1, + "edx25519_privs":[ + "R3VAZZN7JPY3GMX7YQWQ7H9Y3BEH00563AWH9WWW0ENYQT7DCSE703RMXZ6JNM3G1918NFCMNZ2PDYJZAYPZ39N5PSBEHZK13GB2VGR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "RBS57PQ3ARQ8RKBVPV388QRTB0BCEJV962M1G4T4P2X9EE230RJ0", + "7DWD7783XKRZSJE1S8E0GG3Y30X4ZJAR117H8JMJ9AG322P9M98G", + "KGFKKZXTDQR84Z07N52ESVQEBK7J12SVM9TBMM0898WYK5E4WWDG" + ], + "h_age_commitment":"PY9JVXMV10JN10XJ2MT0B8S0W66K22866TZTWPK92R9XPGY6W4QG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"9AK9YPWF1KZ3A4WXDZPM0XTHF7WA7GCKJ7B3BR5QTRPMWRPDQV85XY14VWZPC502RYSFY8F3WYQWVBKJVJ9RT0GMX94FJ1S1E6ZP238" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"F71Y4E004GSP2K78V9H5JSSWNHMDEXG660RRZ6M1GXYM2GAMN29REEHMJ043N5T56M98N8DXR0BM67FFGG1AW50H0K5JXTHJ2JZK02G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"7SYQ6HPX7RX35086QSYV6575EYEMB8RAJAT9FB7SBXGGV8A88REZK7K4VZY25H8HP95YYEGMKH9R71P3ECYHC5S15SSNW9VP4P0YR00" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"Y64ZHKWQ5QT7ZJMK43ETF707PG7TVG3F5B4X35739FP96X44WAJY0QW596C50GA3CZFJVNY7EREECAGB51MAP7NZN06MX64H7621J10" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"EYJ1T1T0EHT83N0HAK99GCK5X00TTYE9YTCV7JM4FPZY5QDR45J09M5QW2VGCM2H3H5NY1G2X9DMKTP55T73S578BCTWNHQ904BKC18" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"XT8WT8RQMSE027DFNE97TR8Q6XJFE37ZD9MW5HVS3D9D0RWN5SVVFQ0YHGPA4YTYY63M5C70AYX3EMA4CAKEBH0KKT3506C26RC6T18" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR", + "proof":{ + "num":1, + "edx25519_privs":[ + "KBK7289ZG3TNESA30EGHNCE10HT9FKKRMCHTJ1TGR8SRV1JYFW3JPZP836VS9PQPZ0KJT4N6FWJ1A5DCGHRBE62W0TTX8206FHTW7Z0" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "25SP8ESVATA85VZXGZ8SV6922A9CHRBY3MFM1ZH972EQM2N9T29G", + "JQ4T1RFJWYBCNXFEPJM8DYB8SDEBPS53BBQ344KKTGJX1VHH8FX0", + "0H3GN362YEE3QT7X1E3RRVGTBJD0ZEJGXW0J3M7WEBYNM7N8MVRG" + ], + "h_age_commitment":"0RR8XMSFN74F2T00JJC4P983WH49HTYZPMBERWS0ZK1C9ZDTFYZ0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"P8X1106VHDDQV5DCZDK4HZPB7G37K5SA5H616T0JX23GFC3PM2VKMM19A611X5X1J42SYPKS3AKRJH4Y8MJXVYC3N1B81Z94KK6G238" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"B8CFK7ZP858YN6198HRBJJ76Z7ZZYW47S7W0BB5CHJM2C8BE3FAA9Z9MQJNPHDYJ1TD95YZMN4JYYS7A6TPNPCPKMAGWVRS6H1H7P3G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"VSHZB4EZMA18YTG3S4N6B58184WSMEJ6WF1JPMFVFH8XJQJGRFS7RF7PS29Y96M9DQCNAXEKZHN18GHNQ899W604T35N640K5TAWM18" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"A97EJSB5VGQ8JEX2DJXDZEVTS4TND8FCQY5SXFB46FN4WG8SRC7HGEE57M5RPNT4PD97VR8EJ7YP76XK54MS5MWX21F6XMWA1MWQP3G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"1Z1GY6SKNY974HGKEMN9W9G7PMDCECH1JKK91BBD4BCJE8MX2E8NFVTZXGJVQYWCVS93B88G9RRXJDW2PNE5571ZPZK9N108G9G9W08" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"2XVHK83TK3YC6XR7FVWCT0VPDD9421SP71K7WQ13WSASD3EFGZQCB6KR4ZTE3P0R87CGPJQ6316TA18QHNDRNJGJ04EG7XXY70G5J1G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"6ZK8SX4NQ72VH0EDQCK4PB8V0YM9679CWBE38K80QQ4AMDH51R30EZBTMT6Z2GM5ZFA122GP6MMEW2B1TYQYP7E63E7Z220J1BZ6EDR", + "proof":{ + "num":1, + "edx25519_privs":[ + "BKRMSYWK3RPVBZDVR4A59H6N10QMGA7YPS29S4RBQ9KV8DJYJ81PYD1ZAY95CBDF8B5499SKM6FS3A6M96EGVSRGYSCW1JX1HS1A4W0" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "WQSWC0T9SJNF4HYPKJ290JG7A8ANJV3ERPQQM91PFCNJFWBW3M70", + "09JWZM8HWCR50MJVKS0TEAYKCGMCZE895YFM2HWK26SZKPXYTQP0", + "863XHWMYR5K00Q1YDBAZVPAKACEZAKZPAJ8KADZHF129H4MY4AY0" + ], + "h_age_commitment":"2MW1YP4PBCM298JSWSSDFPDXEGFF1FMRCHQZGHS21YTVMF4873HG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"WZDE2MVK4KZK2CV3XMTCZ0YS3E36T2M3YSJHNXTXTEE8DDV9Z6S7N1R7ZTMY3S4NZT4GA3X2SFKRXVT06CV6GX6RX00MX7T5RVVXG30" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"74RX0PXTX8BJXJX5RPD6E1HBK74NH95K6BJ8E644ZS8H45H11Z6A9KB36C19KK2B2Z45DJKDEJFHZ7DX9X4N5CGFMPR5BGZRTVFFE2G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"C8NSNHH2ER4CFDCATN51R6XW247TR1AA94A32VECGJFN9SDHWB7BRNWG9ARSQRBF8XP5S5S2FDNQDHQBTACKQM7Q7425M3E2G30JY20" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"4KF0B1NCRG6RANJ3WMYQNBH7ETG04PK6KS378MB6J8WGFSVX87QX7BZKJZP9A8YGBJ74PSGPK76XKR2854XT9YSYKAG7P71FYQD543G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"4BXXHAC2RNVCZ6B7J6YVC8KTX886G8AM6ENCWCH3K3CN28X0DTEHDEB3EY7RPZNKP62KYSBYRZVZ1CDHYRKF0BRG5RQB67YJQJVG43G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"BGN3FY1R4F5YHX0Q0SPX552NJ8WWDZ75WPXDZEVN93EF4YJTKASY5N02B3V7SHN3XAB46F4S8SW80EEE8JK9QMNXD3STYXM7560EE0G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":10, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00", + "proof":{ + "num":1, + "edx25519_privs":[ + "M2CVZGAXGV9TM2HA0N0GYDFT8TY14DPNPRF1FHWBP7CEPFEBZNSJFV98ACW2ZMHZ22ADFM1Q94RVFX0WJEG2PNM2EETFQRY05N4MFA8" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "KRZYBBZ91SQRA6JVBKSRW8EQVAQ2DE64DWQADAAT3AF2TSEEMNBG", + "ZVCHKS40C3AZZFZ3NGYRMVPNXCMEJJJQJ659Q6KF95CW4V908ZYG", + "VP8ASRJPJ80PKEMYSP7T4TT4CM2KJ37DAB99Q3R4ZYHQZTSTNSX0" + ], + "h_age_commitment":"2HNPSVKPY2EEBZA62QWV67NP1MAENZV6NWPBYDN2FDAS6HJBF3FG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"E4536V15YMKWNTAZ7W7BVVC9XA6P6WDAB71B466WCMRVRZ7R79S41KZ41RHQYXZ9X7RAN61DQZPSQ9RQYGHR37YSBBPXWB37139K62R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"69BTHTXK0NM74258NV2KZP8F7RX1841Y0QJJ0Q91N67ER3B448RPMGJHCGB4TMFP8R150MPGY9HDWC9E2W1Z1C2A26X5ZR311RAR63R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"SHT40WBVGSYXNK877QPYF4Y4Q2S42ADT20S8YJE7FJDZ5EK3CDY9E47S6HBXQTATAJE211YZJES26PK5MW5D8D3EC60ER977JFS402R" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"SMS6Q8NX4Q9STHXF6EP8187KC7ARWGFREBB0NQJ56PDWTZQYT911ZMQ1HXJ4RQ2RRAPPECNKV3M2N1BXAZW6SB1KB69ADCVXVEQ6P38" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"EX809P43PWAGXAEZZEAFWXBJ9H1TYKSY37W1XFDJYB6ZFZN2KVR0XP8TYVEQK1J0X49M05NTMXGPTT4DDYNKNNNCQ6QX39SAWXR9P28" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"ZNJ2N85HQF76Z33Q0ZKSSJ9E870ZMJG0DQDN3A11EYT9EP9FFF6GN5Q2QE0XSYTKA6KNN6GMT1TWYH2KP8HN2PP8J7TDY5PXS1E4A38" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00", + "proof":{ + "num":1, + "edx25519_privs":[ + "Z61V46HD81N2TASGTQ3N87EKWBZM0YQMMB9R760NKD264MN9Q85R0WCM9TSE8EWM8WV2MAXK8XSE2SH05XAVJD1MHX0MGXWWV48MYS8" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "YHVK7865XSNC4YV8QMWFP2XNWVNNS05EXKBGSENPK6XMGZPJAC40", + "FFV670EQRN9J881030ESV232XHQ9DWKFJWE6B80B03QFXHR8T8QG", + "TKQ2Y2WJAP48QJABWCK6Z81Z55QY428FXQD0G9XS1RA0BD40Z0FG" + ], + "h_age_commitment":"E6DE28EZCK0G5NSNG4NW0K1W7CB4C8AAJGEA612GETKH5G8CGQF0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"0FCVB0SWPCEDKNAPEYT8C2270PKGVK7JYFF8MAPQ5S8AH1V8HSCRH9HF0BE4R1QA6CEMCA32J8JF1WMZ4JCCARPRPWM0MJ6NTRGZ43R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"34PX3G9M9DV7SNXV20FMKVAY1SZV3G6F7YWH401SNRXB99KABT93SZ6PP0GWAZJP19T43Q1X8PRN60708BX9C7CBKGK547TK4R3TJ3R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"6DCFK5P5A9SW5W45KVJ6QP586FGJJ49F19YCX9V8BZFGWF30GZ8TVM9FC9DB0CD8FST39QVR139QM568SHXJT53Y0E1ZMPW3Y9WEC18" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"02DVX0RZ5SBH5S3WQHJD67TTB5TYJK3AT2MQ61VRWF88JR6KWJQTRNVDQD1XJ5J8BXX047RT5XER9Y3C3TQNGCMN9EAVF1ER9Q24A08" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"AVJ97FJKPSMVXB8ER5FRSYEQ5JE7QCDQ33J9GANH8736SKSCS89TSVHFB5GW9T14VYGBC3K3ZVE6DDV9GCYJWFM3G1N6V520QWWRR20" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"YEA8TQG9MV380KFXJSV8W1QHJEWPQAYFZE5BF7MRA0NX57EAPCAP5VMND0S6TME59PBEGW0D5ANZR93HNBCZ61M1CEMQ6AKABAZW818" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"Q0JEPTPFJSTK9KZZ7CB9HGWC52DY5ZX1AWM2YHTWSST63ZBGYAMM1PXRQB676G821A2TKWN0AZ7FXTNXTK0VQ6KCEC4GTFQJ1295Z00", + "proof":{ + "num":1, + "edx25519_privs":[ + "Z7X0Y36GHMVDKCT9WQ03GCKG93HR5ZH55107ZYK8NGW9A0XZB86VVWR99CW0A06WY5CJMHJ2AEST580E43F4RHFMAZ8KMDZ0W3807TG" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "AHYB6311GGXGK4NF3QJQ1ZQ0YAHN9HCMPDDVZM4P6E645SE9GNX0", + "B7F4HPZ5CSR8VSE026N0EW57RVZ74M1H6DE1ZF3B1AWMH6VZM2Z0", + "CC7Q3DY2EZVFXD1XWWPGA3AMBZ97K6E0H5GT94DTR77047VVE1PG" + ], + "h_age_commitment":"ZMK69CSFX7KM3F97N220DJAWV621ZPPYR244W7C8Y2RYJNVXZG5G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"FAWM3FBXJVET1ZEJ6AWA43Y4XX0J7HH4FEQCD69B9RS4MTYP62T8BD2X40Q15W6W8WDHFDH2PHDV6K74HNKMKRD03XS47EW3166PA0R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"N0WRQDZKVX5XVTH52SCPXJ4A29A9CMK240C1JGJ6Z5AF5EA9314PZ65E0XKHC40S8VVEEMR183K4GC06P8B3QEVPPV6YJFVXEB6SA0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"C0JGXJ5FTJ5QQCTPJ8AY21MQ9KF95VFNTSXAQ980HQRWTEC59HXMFXZ55VAWK7PRG69T8AJHP28TYMY2S3JGH53RDW9JRHJTHCV181G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"F5NT2RR7FDF5PMA8AFKNTQMAVATFZXV16CX58WJQMZDCTVVBV6MD7HP7DA452KXS6G7EQS0ZB2KEDBKJ8WB3MTVQ1XANZY868VMG82R" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"KZCN60V7QAFADZYF3CJQW1CXE8GB8JKSD4CTCKHJZ05Q5ZFTX9R3NTX8EH0YWT1E5CMYKG84BDEFNXPAQ2J6WSR968TR4M9R0S0ZT3G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"Y1NFRFB7Z3CBZNYX8XXWVMDCMWWY60W1FDSCKM2MGN04S43V7Z7F3JCSGMRQNYX1VHG49AHHYVCNXY2XR04MN6JVZXWNDAQZ8GWMY3R" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":12, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR", + "proof":{ + "num":1, + "edx25519_privs":[ + "H3JNDE9SEDSYER95C2RA7AZTDNC01W0KP5C7KZR1YZ8D4QWXQ9JSKZ91AFBAQ27WAFZ2DZSY2ESHHKDVQ1DW2E856PN34BPX473RJYR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "67SR6J69QX640DSQEQ2XQYMCT45K9XH300H6GJXTHDS2F2BNZCZ0", + "3E2HA84P6C6WA5DM3SJ3MVFTJRNXECPNBH2ED3NJCPQY84PVQS30", + "C4MAJ3PMMQGGQ4Q1N35B3P1GPA9XYCVDJGPAZDTMDXBMB0YKVP0G" + ], + "h_age_commitment":"SE82ANRSQ2WFYMKM701QE0BWSQKVSP65S5RXQQ5A5VRXJ3284YK0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"F5ZHFTXARJS03G3BZMWFJ7NMBNF7YYCC817YCGW0GKZXVPS2W7C3HYCZ1CG7X5AS5P7S4Q3H9ARCZBWTE9HVHS2CR1QXXJJ16FYZY2G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"382SXHSRZ9ZCC1W31CHBPYD8DW20WV2ZS6NMN8S3JKETSWYP701K85MBGDH10910Q3BZDC3GZ30S15V8HQKATEBHWQV651F6FS8YY2G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"H2J4NF92Z6F863MYXQVEY4CM9NZ10AFY4WCBFPASK3CDMYCFHB4EYHTC8GZHDF0171Q85VXF1YGD0W4B9ZN5V8DZXP40BBXEPXYK820" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"Y0TGMF01Q7DG2ZNPPWWECJGMWD14BQTDPFB84ETQFXARWN30JEAKH40486E7WMNTWMYS6NMTBRVN4E3XBT8SWJNM01YKJPEWD6ZQE08" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"HA9GTCBV5WE3RVSP0K7EY0BV5MJ9V5ZZ19C8DQS538HCAK9J358TJ3QY9B9EG5GGZVDSYAFWGRP8RTFHWK11CA32PSW2DTVZD0W6J00" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"2CQSJGJBRG517M8X16H5HPZJ6S4NA3NBG7DDK1ZFET45N19VHTWD8NH1NS8R3H2CF5H4BHWZK98M7RHPF4Y6RA5K5ZMW1NYB1GNWW2R" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR", + "proof":{ + "num":1, + "edx25519_privs":[ + "A0P5W3JC0957F150RR4KSW9F2VPZ5CV8F2TKT85BS70RV7JJ2C5M4KAG4CCH1ZJCEWMS94Q9XGVZ1XPF2JZBTT3NYM0HNEYVWHRJ3GR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "6HWSKF7H6GW0976PWV41DPDR0WW7MR5NEZNSBV015DYC0J1CJ690", + "X08ZETE7DRZA729YC10AWFRN8RC75F63NESJKX3ENKA6NZYSMGC0", + "WNTBP10EYSSTCW6B3HBP4V12NFD3FHK194N9Z65SA4NXVCNBFXJG" + ], + "h_age_commitment":"ZS03GCR79JJE24FNA04AYF3YCMXZPQGGH8QJMXCS7JWEKKQV8RA0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"0PYB9Q5S87R57DCRDB53C96PX15JMXVJJMEK2709PWVVYHG30TT3HBR3Z72DFNRA2MRWQ73HVH1EM5GNH2BY0GYNQF3THE8SQ4GJP30" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"HNXSC5V26RADE95EZ8VHW2M28Y1RX73W7RWCZ1A805NVRAQ5YJ2BR6VT28WK65X0AE0S9DKTMKC5ZBXV0P81K75K08HPKS5NAS98G3R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"FFEWBJXTS3T4D1KF1HBF547X6W6TS0MMPSZ42AGQ0MR3FERY56G1H2VC40E24XACV1F3BTS7FQ9V3M10J43QXHN9X6GR617FBA41T38" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"YQTD4BY7HVEXMSBX954693TG782QCR7FVNBMSG7VTP9Z9GCVGEPFY5XGK4J36A04CEEW3ZC75FYGMNJ35PTYPJCJF27143TVNAF7E28" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"AH403Z8HYHF4CNNWFB9GK55VCHEDNJMSGMTGAYEMVZFPZ78S99DYXDYPXJW8A2KBVDNQPCEYJ8MNBB4AGJWTF7BDXN30T1YC2C4900G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"FZBWF6MSZPQD4HBE0PK3TZT17SSNAF8VAG3NACXFRZRAGMAS5J3VK9EVYZKGXBA5JAW8JCGMFGBX6SCWEETXYG7XNGYFZ6C30EV0838" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"6Y9RQQEGFEEBG6GRGRWJFEZPZYETRFQGR7CVKFZNA3B7P8AWBREFYTESHQ3VFQ0R1X2QXB04AY184YDN2RHNFDVG8MEPNBSWDCQ12MR", + "proof":{ + "num":1, + "edx25519_privs":[ + "5SA830Z5MRDD60G3Q4040YE1Y82W4KBS31EYS4T74FGK4P1KR47R4RDWY066KZFRH8PFVH8AYYMFDH4R80SP16NGHT2D2MVEVJKDNSR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "NEM1QBT64W4XPEDSHT43BADF6K4D9ZFTBVAMZ2YTEJACJEPC8XT0", + "MGYBGPEHJH368RW928PD5C8J1S5Q4DASPTY59HDVJWSYXD60YSVG", + "9RMX0TE89MWTRCX10BAQK7KR67RK922BCR3HF1Y72C2NW9G87ZV0" + ], + "h_age_commitment":"DK17MJ14XQANNY5YRB8RVFNTDPSWRPCWWAR1H7DCD4X0TTF9FQC0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"AF06A62WZARAFC123B8YZ7CJ216GYGWAHM6D9SFQTY1V3Z85VP4B3MFR4CEF93RYFMGYH8AC4VJ5MDJ7750VJ7PCR1FS9PPGWQ5W838" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"N60F3AEG18K3CPFE6VE1C33NBXW6KVKSFG3YDS6JJRDBHKPF573ZA94Z2XD63ZGAN86ADTVJ4SWK1BR64M0BTNE4RA6TA35T29ZAP2R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"DCJJXB8760XREBKS9HR5F2VB8P33BKQRDTGZSY5FD3X5HGGYY5R36KD7X1T51VW75KF6RE7F9MAKFQDPP5R404ZPF51WRVHEM87J60G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"PQYPB60NY713ZZ6SQZ26DBB82HK9QJCW4CP5YX3QFZ03BX714EPKQ6GAWW62W879HNHRWYPQD8BR5W30JKKHXNZF3EBTA7RRM83BG38" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"PCB0RWT5Q3BB275G0Z0809YDFA69483YBEJT4ZRKZX0C42DAGEAKGSCMKMH17WHPFWC5543DVB3HKMG48P1QC816EAPT0BZ077ZE028" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"3TG18DAK40CB9ZW4KCJZ5JNV1SW5E2BZWCQK56NCBZ3QN261NR1T03N8AKAH6SGJRRS1N9GHEP50FKK4S8NCK8ZV1V9645JD73TEJ28" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":14, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8", + "proof":{ + "num":2, + "edx25519_privs":[ + "G07ACSKMZ92B4J5AD4RQRP27CPE1870MFRHBC97M307D393S1NPVKX74GZVS54SVPVTVTWMJP1XS76V7VCPQ3N2SAADXDD19MN52KKR", + "M1JDSGWQVSW5YH08Z52GAV3RY0KWGXE55PQPD2GS6JJQ1KH3ZH60TZ0ASS2YWZC2AWE3S05YGNYM2586CF7MP7GBTTR2DV2B7ZHG1RG" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "XA2JBFCKXY1DP3883MEP8WKBRWMYET0CJXPEP078MTJEW1CXV7V0", + "88WMYK7H76FNSNA1CQ7ZGDDZ6Z9G0X6M227HAGMZ88MGGKV583P0", + "GKPDJD7K23FVV1DJDY0FCRKQT2JEGZJRJ5WFHMW6QBJB2VGWJCSG" + ], + "h_age_commitment":"2BKMSZZJ1G8D73S2VPWJRA08N84CZQK5DJSCBRT7Y111PT16ASH0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"1073TAG2MX3XC8M4QHKPYG6S153HDBN2CTTF5CDGAPMEDTVNJ2KDDVKYFAY13S19T8P29W3CYSAC4EJJ98G44JEPFT5T7QD2DGDDY1R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"GRKE620VBT2W9SS1PHEZAVDPNWRDGXDJREFYS8HK5VD7AFQAK1EW7P1N45KEPV67Y7AZV62EBVKGRNMJHY24YT7C91YNSWSH047YG38" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"CWYC95M0ECZBXRWJE7THW7FB05H5J7VF2KV7RZ8Y890XFC2F27Y9EVQVEWJVAATDNZRFW5D4YA7F0N97F7DNSE4Y0YKA1A1KBA9A000" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"3FVKYHT5FC8BZPDMNTWN24Y2TZWAVPS4B718WG0J2X64HEAJQ4TD9GKJ1M6G4H8D6DEGH0G5ZFE1A4KQH9JNS3K6Q69AEHPJYBHNT08" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"ESJ5M9FVSCARWM762Q1CFQ449TDZ8FDBHC1P9QHE3HTC03TZE3GJYFYE1J5SMN9660YQXYAJ538GZBBK6AR6HS9VWS530TA027HTC2R" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"QWEHWHRSCB4ZBYF122GPH26RRBHAN7HC628H86SFSXH0TQ1SH2MX2JHF8HD26QHKJWAWH1C17QCMNG34WK03GT01ZBKT8JRAPVJ5E2G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"TA6WCNRK7HN57Q75EQKEZ2T2EX9Y3YPQ1V0WF6NBXJQZB6MXZNEJS42H44Q0G6ZGPB8NX0BR43AW9R0PN5YDPX52959MM05VE7HT830" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"1ZG1N48C7Y0EEZA94ZXABZVV04QPB7AEBBR0P2KQ7S5HNW141W7KXHJJCNYYDGX8M2B9FN08ACJ8KJV27XNNPN7W608462XK2B7HJ20" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"0NKCKK72PV6ZDV1HA9CGV4N4Z9W6BETM6KJTV13XY682CJE9KEPAKBDAVWTCX5KBH75P44QNMR51J88K19KASQ975G5W6PRRM1C5408" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"TB7KTW3RKSH04BVCGPXXHA41GHNNZRMSTBAGAPNC7J9WG5MNNCP7CR8H72NR3MEW42MCVT1GFJDJNJFXM0JC0H2XWS3Z1STMF6XH83R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8", + "proof":{ + "num":2, + "edx25519_privs":[ + "S8QY1KNECA3120GC4BPNJHYNXNEP2EQA61A2P4ENAYNTBC5KBR3R3EQ2NG6D7TWRFMN1E1Y8YPHTBM5E695TYMCMJGYG0J5AY2YSRAG", + "A62YGSZ6WZ4K3DKB5B74WRQ5ZD4F1FKWYA1K7CF9CJZ58N97JW2MSXPQD03EMMGWJXM68NF7788SBWZX1TWTRDZHW06RHAMF9PWB3XG" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "6TGDG8PDQW2W7SXM2G772RPW9NE14SEQKFVR3WKMFHRQDM42SZ3G", + "4XJ8FNW2NERABNY6FNT223FX5EKWXQR3HYAECWMMW6Z8MAQ13XCG", + "DHX063DA3NESRWNY39ZW32CXZMH3VGF3CZGR7Z5KRBGK2H54TPPG" + ], + "h_age_commitment":"XZDK6YE1STZFK89VWEVPTHK1J0Q8JGKXKDT10V561ZZ729DAYG1G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"PE9RAE0R2JVYNYJZC06P69MAQF38M8KJVFYY5NGGPDTEZG8VT2YVMBW2KGQ1P42H05BKF1N4XY1WQZDXEY2ZEKV0MHZ93CEEAD1RE10" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"ADCP4WJCYHNBQX8TCVX1114M93XP76RM55M7ARG03TJZ5K27H691XNDCY7EHYC9CV0PN2B1TVNYCGE2M9A41KCEV62Q4NJR2JZTX030" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"9GV8CR21C5NFXF0Y1Q2EM2H565CVX853XG8ZWP9Z7G9FTYCFZJV09XZ9W3QYYMBX3Q6MWGYDXPTD33AEJRG2A2Z2KZRSERX6V5QQW20" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"VNJD2PZTGMB1RPY0MX17A2FQ0BGGE3PP1VE19VHR5DRA31VFQN30Q2BN39AQJ29VJF7HB3GY77FKQYJQ2SQ7TVGPS16WJ0N69YA4C3G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"7AH34Z7HZNZ6PRQ57DGFT6K87W78YD77RY1P1N9FP7EWZTMEQ1HD90H0VXVE7CK3FF702PB1771EHCEYA19JQ2P2R65DA3R99FQZG1R" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"20KHHFF90K8C7EP852V98SYKHJTC8DY1VXMJ8GRWKAWMRAGNDS0PT1PCNDHM06PVZMH9DJ4HSQFMZ03H6EDMM7TVW528NZYAQCA7A2R" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"YQPVZ518KRHVVX5ZWVVBTY5KHYAA4YVVYCWCG7YYJQ26FA4RQM7Y4Z2C5RJ0QJT6S6B9X3B04STYWJV5R7V8Z52HM8RZ6MH3DRPNR2R" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"ZYESYV373KQBANWQZ1VVGG6MWMFPDPJ6QKF9RJRVMZ51S7VR3JM72WZ1KE3BJ51H9JR8J8VY519N9YF3KE02NY71ECDXDDA7ZQ18T28" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"4FCXQWMS5587X35ATQBYP5M8WPNDKBBCGYDZD723B5TRAVD0P6T3ZW9APZ7XZ1FX7AA7CAXS7BYGSN89TRQMB2ZAEMPQS6JV2E0722R" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"99PH8M8VPR3N90YSV2CNVFVP4RG7QE29YDZRSKEMK9ECPZ48YEEAR1KB0R8Y3CGYBHD8KXAWRC8XPE8ZQJNT47Y8P36W5DWYG1RVC10" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"HMP5MGB6ZGK6AFXV1J8NE8FTD6VM44WJ8SHBM623HY92W1KRJHCBGX5T1RVAMY8B63ADV9Y2K33NMREKQD86EPH4B6JVNVCYY5627W8", + "proof":{ + "num":2, + "edx25519_privs":[ + "6TC4ZZMAGDFE0WZFT2YGKA6N4Z9JB5EF6NEQN8ECK570WEPJ807033DAJ5DNVJNYAKEQ982MF8KR8Y0JHB04K550RWDVA9PJ2PGZ16R", + "R39Z1Y514R1E1DZGWC19VW0XBWD3BMV1Q4GJF6B6M97402SWCR0NTG47DF29M3YSKZTNXPHNAE98G233762ZNWP63CXTVNSV2PB2E98" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "N82HCGRH9M323E6RD58SK6HK70BTS1FB0SY0BRR8EBA2TF614FNG", + "8A63YPGFKNJVX9TX50WGA1KQ4ES9JFDVB23D9NVGVZ2NXKV3JZJG", + "RS730M037TE6GTZNZB1Z8P6K9SHCG5WMCJ3N1911R4DQ7S1V2BQ0" + ], + "h_age_commitment":"6D7BPMC991N72VF6YH13CQAV8GWZBTVFWQNAX0YZNMD8XXS2SJFG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"RBF8QF2ZX1PPXY3FP9DF2YDRR8PF26P1BCCQJK5WW4WXTC3JCP6587G4X84WPYKNNJ432P80F0G40F3B77Q8RTZBW9D92WNGD7HM02G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"MSAN8Y9D11T5QKSYJSTAVAWDZWPE3XN4FSF93NTD5C792YJ49CMWKX3G1TRR6T0R6NNS442828VX51EC95QT3STTC64D2BC5Z2AZM0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"676XXK7FJCFAB470S0TR4TJT9QDQMG3FZT02HBJ5HJFYJYF3B9W3DRGJPY2J2QYE5F1GPT1RGNTKYMJ46EHAMRP1PZQN0S4TNG23M3G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"PVZJJWYV66CVSF75P4XSADXG0S35XWQQE6HM16PM4Z3Z9XWV16P6610QJ84XV02SJGQ155J00BM7FFVRZAW51M7741TMC5692BVTR30" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"7RJE8PW0KPTPE29MHVZKETQD3WQ7E3ZYYQXVFNW6CEH1J6RY3254MTH9XFJB6DN53RCGSDPXMEKNK46818X9033GF1310WMDHPZ4Y3G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"4V4KMK3R03P89JP36R0TFWQRFHGKTW4TZCJVZ2RBR9ZP4XY1DF62NYPGNB3004B2QHAH5D9J27MYVFFWY34WDP6WPE267KQW1TTHP28" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"WWWXR9C235FD03BCFRSN2CDFC2JYM5W9C04JYRHGNDQVHE2JJ3DMV68DRTXHZ02B4SNJ748EPV6VJ9G8C5F3NVF47594RAJ65Z5W430" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"MCBJDPT8S8GRX58TP470Q5DQ22NP5P9PDPBV7KF22775K59R8J6Q58NF1K40HV4B6W4KFAFY30HER8NHK2CCAJWTVFX3K6XFABNG420" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"06PQVS1KM66AAME252JAQ9A591YE4CAR7MQQ7FSCEAM375E80Q7R5N700FW897ZB79MWDKG78MCQP5F53GZXB3Z4P1TGDS0S653GR18" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"8NNDC4HCBPP6Y60EK7G1SXH24Z5NAQB53E4VBH1FFMM8EH98RR36G24RQFQ8RNHP0G3NEV8QCEW052DPT9H6D6D35XMXB6Z5MMQKC00" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":16, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G", + "proof":{ + "num":2, + "edx25519_privs":[ + "305AGMYXP2K3KPH3602E601MJCXHDM09HVSBZ2E1MWG4WZ3JFXXAJ55RZGRND3XC8YABAYF37XK6TDC9KN4HMTQQN71024W7XKEQQ4R", + "W0ADSQMQJJH3HEKWDF3NWJ8MBSGP77J3QHH7H7G2G6AWKFWE5SEZRE0QPDR4JNAYR1HBZSQKJ34CYT8KVWF0HP63KPJXNVZATN3GBE8" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "PCBDBBKK1GZ93NNJYKPS3DE2F6G302JC51YHE8NTR4CHTEPQS18G", + "4TVDKAR1PRTGHZRVQJRWF3S8MQ9D84G7TCC08F8EH33ZK8GZM14G", + "CEBM8Z9SVHDSA10R5QSB14178X00F1NG13YVQDGA316X613ZBCQ0" + ], + "h_age_commitment":"68EF2803JT7NEFZ8ZGENKCDW90P52J6DCZB43J9B9S5MCF87PC1G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"G2483N5W14BK5N67F84RGK5E9NH84R2JTTTCGB8ZWRAD7R3TA6YTS0FYM6JDCK1638R49QPV8J0HZNGJP99CSDWM2NMDMZ1Y3SZDG30" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"4990HZ12JNTVN4YXBX728A9PZCQBG55Y6YC946F9W8KBMVWEMF6RMGE9Z6T8WYCT45NWSR62479S2VVKJ24PY2SGQ1BXWX1A0900Y18" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"S88TFY0KQQXW5NV10WQRDCZ8R4ZTRP00S76WED05VSW1NWKYYKW6KN7WET48PXZ1RY0SYZXMDEDNWX5RM928Y6KCV0TZ5JEPVH9NJ20" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"D46P0PCCV3M9KN9FEMJYKT21HSJHQ7TBABREXVBV1SRDZ54EGTJ18BNMAKPJMW59SN09E7QZ5GQ691GA38AA1VP8ZKE520E0S1GKM38" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"JCKP6ANDCPZ9387JJ7DJHRDHAGF8CMAVR2YWPYS3Z76GW5TVGCES944JDSACHJAX4AKNZ8Q96DVB3GCRD0MJWZZTZ6FQ3T8E5QF3030" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"TKHAWYJPQV0HBYK123E8SJZR00F1PMC1J8FQ1D69CGGMA4X69YV5G19GTXGRCP22XS68CY42J8DW2Q6TXZ7PVATY2EYYD52GW8ECC0G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"52VYQT1DTSYW6BA6NWN4CRB58DHET0C2DA5SKN35NC7H0GW12BW8KN2QVAQ2E9XXRX4NQY99KKFDA01DNT1E5HGKMGWP2R7JN2EFT2R" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"AKF0T06YP37JHTB0TKQQ3C2WXAB3CNWW6SQF37YHFRM4SKEZ0N883HN9Z9PKD6A20N052QA857ERCDEV587CYPXJV2HZ4ZCHKW9D43R" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"67A2WAZR887BTTEXKX2AHG1X634F0PDPASEMND7RSRJXG8AEGAVJT4SE0N2DR9NRQTR1V459VJN163XGV4FEF7E6W12GWBY4JBA7218" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"SC9YNVRFX2QZZ6CG5VGE12TF9M1B7AYPDDN23NMKVBHNBWYFB7YB8CQX387NCAS213D725NMY7BRZB4B1D35KAS33CCQ9HT83276A1R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G", + "proof":{ + "num":2, + "edx25519_privs":[ + "1169FZD52K4JRWNA6SAF60MPTGFMS4HSTWJCKZCMQ1WJDZT18819WEE8K0K6A1S4TJQ5GSDBCD5PYAYPFV7E4PVF8BVANZRM0X98VP8", + "DD8ZN84PTSF0G3S6ZXJ6QGMKE8RNMKC5DQXFNRDX35AEFA14YC03D6ADDPXAVDQHKY5KHD3MH1489WXS242H6WG45F4ZQ7JWWN11KKG" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "39MTEJ76J4AV2C64RAN1VSVDDRFNTK67TEESFN9D602ZFQ0EJ2M0", + "QC1XC3E9A40NR53R02XC9QZSBHESRY7MNX80S7KP2GVMTEZHA4N0", + "4ZPPQ1WQ0ZACWN69QJ18M76K3G8ZK2D2FYY48W7K3WEDC1YZKY8G" + ], + "h_age_commitment":"DCHJ07HMTC443CC22ZD8D25WSVJ0GZAV90RGF8J2S15J7Y5KNVD0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"F1DBZ0CHXYTB3PD46BQV6T34KF50VNFWX4C1R2CB5N6MGWY9W32S4F9KRS1BKJHMTK9Z5G7CPA9XWB05TXGPD1MHBBKAMEDDHP01T1G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"FRX93TDN8BN33EKD31KSDABHFEYSWE7PXN098DMYGH4WHDH4Y6891V89TC3AJ4NN3QR9NEAYRV6H3TVSEGH6XXBDZ9S4BBNBGRJ6G0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"D3HS18YN74X475WQ4R1EFMZX0CRK0N5E3E4C1JTY77WBVV3DJGGGVW6H5TYA6JZGA5QZ9ND1740P4Y7CN0RRPZDA52PEP6MK7BE9M1G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"N897RRKKQFK7K82SYR2NP21J1J0RE01RW01VN62EGZBNJ51HN77007X72F37VK3X1DJDV0MY4YEA4GMH8RDG9AEFPG37EJ76KD3621G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"WXCZV9QP74WCTY1Z7V2VP1TDM1MB2ZP8EAFDA7CK2259B72J9VTV88N8BQCXD6PFGEX3XC628Y2XCFDYCNJSRQGGMM0HXTGARZ6X828" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"CT8MZER98PD4CE0XDV72AS7YVQ5RKH0DQEM8V2C9EWBA113PZKKH7MEBZRN7622YGJRHD94EK52JVMX91X0YAJ3VD2QB06ABMB13C1G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"5PQ43XMFV6VG7PXC91YN21CPF864NMB2GRHZ1GFM7CRJMZ4D2K70PTGFD2ZD4MTM6Q35BHFM61G95Q001N0YRN84P2CZMAGSTVJ0A1G" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"BX9R0SF1HMY340XKQCHFSGPSBM7EC7NVWRRGXC70VFRFRSE5H2X2B1RW308A56BC3PNPGCHEHCEKSRHFS66WM08YJ0RD6DRHNKAMJ08" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"H9MVDHC4C8KFRH82254P7G7CM0X2D03F9B9TP3KCD0R4SNK4TJ0KV7666737HZARBCBNHNFRXK560D6CADS87NRCKH30BP99C88HT00" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"D6J0M40MNTB3Q6ZAGWPE9DGET7FDZXRSPZ8NQCC5V1KP5DHFEJMKGV0HNKZ42TZK4QG0BHEE0RY6MGYYG197J30PSRRCFC43GSH1Y00" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"CTN1YQ3QX3PEHQ8SNGGPH008Y2N0H5RA7WWT7X758EXBMV0NC3BDT41YWSRNFVB9RBKSJ2Z2P3JWC3V2DDHXMF6556TZXH9A79BV95G", + "proof":{ + "num":2, + "edx25519_privs":[ + "5DKXBKDMY0EW5FH9ET8EXM088F0R4H3BF98925EV51M5S70QEM7N20YM2J85CGZMA518R9DAX9X54K9B8XYKNEXCAVPH3JWRHYKXS4R", + "C0S15KAXV2BHJA5CS9MA015YMSJWM588NK6QXK8BXFCBD9XTFR0QXXT89KR4XK3A5TETGK1CW7RPP84NDTBSVM2JPRQVFEB2ENPYQ70" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "EV8H6ZAYZFZ7AJW1NCRGGSVW2919M9S9G6EFMGS8J04ET75WDEX0", + "SNDTMGM1NV54CRSYDPSANEE7CEYH6K2NR14KN1RDS2184DJFCQBG", + "PCSTPW67QG2BARPVWX43T49VNMCFW4M18B7ZQ48WR2AHYHPM6QFG" + ], + "h_age_commitment":"FV67ZBENQ8CN6P4MENEJ0MSAPSTZ2272SEPJ1JMY3EVTNNW3DFYG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"Q0EHAWXYRA66JAS5G3AQ5A4Y8XCF06XYYSBRCAB5HS5G4Y0XD2MG49BX26ENA0984V2W0SQR4Y9PNMEA1ACHK3ST89G6ZHHFRZYQ830" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"8W7ABETZJFMJZJW2S2MKRVET5FZ0R36HCP3SCBZBQEFA45MRT7PJTPSPW2C5T7SJRGQDJQYTB6VS0E2PD0WJDKWB7Q8KVXVWY1P1M2R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"86X7NX6PRE0379S5BNQ9G67EA8QAZ6VHB44V9QHE0XK4G0CA2YZNZ5K85KWZFS0V4K9PZATYDJT39HQM7TACADWN9Z0HNBDH7MEWC10" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"4AG6QZBQJBK318EJFKVAEH55SAA4490RZXVTGVQP4ZETWXX0717CM4MNGQQG663S12QERAWEHVAJ0BQ7JZPZNDYJJXX2H870A847228" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"SR360S9QFTMDFX1JJDSCV8PSX97BJXEA00PVAKXZ7KE28EWWJ54JKD97FJV1N9FX91F5P0Z511XV6HR85459MFFN70YXERPTG067W3G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"P5PCGNVZ8ZY22M15C7VAH05VGCFWQHRZBDC6XY5JM3PS2TTGRZQEYNSYXHF5D8AJQGPYXPH6SHK2F1GB31NX4KHEQCP2C8A1FQ62W1R" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"6SCMC58W99140N1JWEB0B929FGJKJK4Q73DDNA9J7RR4JPN72GGMSV5JQ52S7WP20VA7724P2NJFKRBBN200A9KT4A0PPRPE4ZJNT30" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"ZBCKWHAVSZQB2SED4BM409QRBXVEDNZ2YQYF38PAGPW3M6MQ438NS1YT98PF5A9SDP2F33BTNCHTDPJB52M0QAHMB3BNY9NXQPCFA20" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"BXK3B89VGJEPDRZ1NGBT8B5PJX83V5N7QPP412W489XW9K9RA5NQHYHMHXRMXSQ7P1PFVXA3CWZ4RM50JP4AF16XW05CGVT7J0MTA08" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"QJ80HDPF6KV6E4M4ZJP6JW9RX4Q5F2YS2SRHWZRNFX3NE78FBVNNSTJ6K12BTWK9FDCWPNXS9EAPGJJ5P7T53HZANXV26G8X529X42R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":18, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118", + "proof":{ + "num":3, + "edx25519_privs":[ + "707TX6HM99N6SDZA9CYQSPKJAW6JJF7RTWAQ1AK7TRWFBKHQSXVV8FCV0RRCJ8NBQDP0B5A6FXAGJ9736ECGK4CJF10022PK3ZH7T80", + "Y01TZB16Z3YNXJE1CCPPDCY06JENP3FZ8J2RDSMTAMZ1XEHHG1XSSJ9EVVWHDF8Y9A1GKXMMPGNKVM45EQYKMQ6RJ9XMKHPBS56PEVG", + "81RHHHMPNFYRM0WTQB6P7VJAGR640D2MTJM57FCZWVCQX3YCG1JBE3W3DR2H0Q23P1H8PWW8EENMAS26W88C599G97ZVEQGTREFGRP0" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "2APEV2SVC5PS29HFAXAWJWNQ8HHQ50KQBDH03RGASG24108VKZW0", + "A1GTG17DN3D8T00STVYB5QW24R78A1PZCAKVNKR841J5FRB4DACG", + "B63J8JKM9YV8TCJAPMBPM1GBPET4XTQQ8ZH380T9BXB61AEK5PV0" + ], + "h_age_commitment":"1EETB46GHNYZ9F422B38SJTD9BBMMTN2TXA1CCEYZV57729PKE0G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"5MFQ449SR4R3MV6WB7BVWJ6KB8BVPWTY2MDPV98TATHK525MZYXSRJG5K7BMTC7P899VWWAHJFCYD4RCVPEGR51REN8NGCJ3BDB9R3G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"T72H8A837MCA377G09MNHKHA8RN5ACFTQPAPPZGK5TVV7B8FZXB240HWH401XQM5T04R590TVFV0TZB9KG1KRQCADAFH27KGAFQE21R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"YC9K9C7YM7A3SHG2CKPC8B5QYSVCYFJSWSRS3W7EKDWN3AX7JSCEFPQT3QTQ4A21KC098NH8SEFCAAHEAFHHYX9G50B63BK7PXWYY1G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"TWSDQJ6TEGBTN2EY4FEGBQAXB3T6MAM17YZYMM0EGZ0EDQAW4WY884A1307HVTVCTP6QEMC4WZZDY5ZJW9CWS604YF2PJG5X5A66W2G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"JGQA1A8R5V28YXKA8C4RCRFWVKAFJQ7GCS02PX2VMN6AHZQ5708JDB6YS3288B32SZP5HHMW40S7G8MJ9PMPRYTNKHXFSB6QN053G00" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"4TD5EERZ5A09SC1J42SM1725VPSC01WAGG4KM3SH43FJ47M152WZNAAND5E002F6ZJ2ZY4757GH22X2ACH9HQY2FNX772WBS5WQ6M38" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"AVKJGD4ZGMZ8S1SZ4W53GFDACMGKJRC3MPB3D87BMX8QFP1A2P13ND6NN5K2VB1ZP54BCXJR6H5EBD9FYGGZP4V22CGJ6KAF5GRB82G" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"BP8G07P2NW07RTPFBPXZTFNMWAZJ426XCFJ5257K78MNG5H5BH0WJGNA1J6MWJCWNGRJWHRQ6ZTPHN68X72QVX7WJAK12ZV7WYYQT28" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"0HR7XXB9QCY24WADAVFJZZHYW1J9DVHA8WGXH31X2A4HQVKFYHC9EP2HFZM1E87YTDKRTDDGMMW2YVR6V2184RCW4SJ010793RQ022R" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"QAQD5DWVR606RXH3ZSGTPVJ235BTGXA44DKF33P0DCNKHZT7TYAGE561BR27SP405KT1QXBH2S3VS2MTSWNCZFG50CQXKX7P20WTW10" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"K10KBCZP09AMVHFFJXQSWSHJCN1M9824K5DQC09WJC11QCMMTZMAGC3Q75K9J7NMQSBPFD4VTA7R3Q93TYD8BK21K372ZEHY39TA808" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"0ZS9SMHBK7FNM8YGFM2CRH4V6KGSD60QE89NTV192860N80NQ3DS3MX343M49G9EQZMY32ZCD9EDWKKAGQYBW54MS77P9P4TBS4VY10" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"EE5TRM5FVVMGF04RC1A5ZETRB3RHD33C8QZCJ8YW0FBH0RVHDQW5QPSMTMZJ3D41HXRFET908ZW992K7DHZ12S4HJKKYA9G0GM5V82R" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"WAWES6ESG9HM4ZV26TP2KCPZAY1DR1NN3XDTEJ053D9FKY92WZ1SR7RAYQDM005PYK4P3ZC3CARTA8KSK13ACXM2DFWSME68B92X02G" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118", + "proof":{ + "num":3, + "edx25519_privs":[ + "2EKRBSCJXGGXNBS8P8J6GWRFDMC92AKH766T6WQ20K25TTQP347XVD5JB87FAPB7FRPSERC1FR929CE25W1T2AJ2VEWB5T03YTRQHA0", + "QTQXSXFFCYA6TA5SRBJ11HZMJPS360FHFN2C1DJ5A9EXTZ6G3808V5RK5PHTS7BWYD6933TTRTSQ92J1YQS2WEQQPFQQ3TBDZN83C4R", + "133MYMXW2KRNK2GENCDRSCH3776AQYX8Q4C6R9NE1Z8CAGMJY0556NJN0HHZGPD9XB4HW80KD5Y1MN5YS9ETNJVVH0NV68JZFAAV710" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "643CATW17QH7CDB9T9JBM2785WDJDSPB4D5CAQTKTMTMCYYYBM5G", + "MG0HW4NEW9SCAV81KHKRMX78BTRH8FJE28MZCT6GJ61TWFH9J55G", + "WXWD1KH4W1E1NEHA16E8CBEH2FQKWT94MHH6BT9E22QVMDKGJRD0" + ], + "h_age_commitment":"2RTXDR6CN0Z91TPV18T7FJGS22VDSAHH1CB6DE2QPDQDNGWXMP4G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"EVB27PKAZ1TVXVYKTJ8HDE3MKA90M6ERCA9YQ64QSY52QB7SZVY76D9FGTMWN2C3VPMSPZ45JNC3RRXDVP4WHCJNP0CZ9K0QE1B1P38" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"XAJ775QSQSNQ9W112JT6E2YWE2VF9984FDPYJF12FTEQHM3PP6YDM6Y399R2YQWY1JCS2SXMVS13N8QBSQRZFV5K0RYSEC06SS9GA0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"M785TKJFYW27TJVVBVXQKN3PDJ7ACH938HHTBAM37MEN141DYKB196B80369SNW0FRR4KJ6T6GA96CBDAZY281CFFF7MYN4M2FNN22R" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"E9CV0EMJAQEYKBBGF0MQ4FXH0MRWQ9S11C7K3A0YAAZQX8RZ3PYBZNKJ8Y1QN0YVMJFVP4JNRY5P1JCC1FH5VVCK36SJJAS2MQPV20G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"TWBJCHKK91P4RAF1889FQTXX91NB5KYHXAED9J7AA3BC49A5SAXT4YA6KXBXD5T0358HA4K24T2QWC7BTNBQ1QG4SMHERJDQQ3ZCR10" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"5SPJWEJFGZ6W8QG57EMACRJ6XXPWJAHFGYNJ86EMH1B0VTTS851KD59GJJAV1C6A83SB4VQEFPQ6JA8YPEN82HQ90HAKZJ70QCCRM38" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"VWZDFVS3RRN9EH054B3ZZX1MCXN2BTCC8WS1C43W04R424GG63R5CZ7P245TGYCJ4V4ERW8RXE96G6330KM4XW826JWT44TWGG5H238" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"6ST93K43E7J1P241QXWEC247ZDPWMN4Q9VDZANTWWB7KSZKPZX3C4PTEYXH34ADZDE760R8F1Z0M35X19Y4VNZNKA7S4ZPKGJFTAR10" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"KKMT2S6VGNPP7YX6CNB9246RPJE4APWT5REY1W1BTNTWDSCZMHGFK4CRSMZ62RRR6TKDGG0QSKZNQMTQ46TD07REJC1Y9VCVD8PCE00" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"87T1EEJ2JE2DQHENGT2NDG96T9TNCP3V3ZA72CQ5HV6BXTE1P5HC43FAFSHBB3WY8ZRNDPATDD40DKGB0MS1ZB5AA6BKDKVFTZST42R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"7RWSXKEWGQH2ZSXZE2SWR31GSNHSAFYXWRVZAPW5XC2WYM1W158VN9P0AQMR268G39CN3CJCHJCGWZFP6YS7D1EP3SFGE5X63011628" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"FV12MV7BZTCJX7NERQKQZPCDP92NA29FNH6MX54016RBGT1T0BX2D038YEVWB6YHKJMNSH3NSZAQG0JR1DYHA8M6HMJR8R5DCDVTC3G" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"K1Z6ZF6N0E7BHBZ4P03CP3B9XMVA7Z26TJKRVD59TPDC6YFWJ3WFGYW0YBKX961JEXAV65QNY52BE93T4M4EXFS4YX0RQMEBVMTDR10" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"YXNBZTN470GP21VHN2Y4BQ80H0W4QN7MVG1RD60XJCX1RCY89AFB18KRR7M1HEDV9AT29P9HD1AHHGE8EN5BN3XKC33970HVQ3TP02R" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"8XPT2709ZZKNP7YQ05DQS8SG405S8CSJ6X41JG8SMMTGGF8DXR8007SZW4H5CRQG7J2ZZQW2J2PETZXA7FED8YVXVN6SEES4T6JT118", + "proof":{ + "num":3, + "edx25519_privs":[ + "4TS84PS478W3CEDXME1NV4QR89YVPS647ZGKHWEME5XS84MV107DR01RQ7ZGAK2SKG5Y992AK59GJT08VZ5EZFWXJEEV1Q96VTA94V8", + "6C94VRNS7ZH010ZCQ11R5XB8S74S77T3AYP2XVXRG9RMMKS558539T5WD32S6C02Z9Z7P47VPA6ETZFHH3BZXJM7YY5505NWNM4F0N8", + "XM77B9AP0SWQ8JNXNTJ6WA4873WDMBXXG3QTVZDAMVKST4FCWG0VFGCMZMNHESNZEVDG85T5VH0MQ56YN7QJBJYXSE7TT05RGFG11JR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "VCEPV1EFNFFNGVKEGTDWVMMX2E2056EMX9T5M2H2YNKK224E84W0", + "TNQFVSBB715Z8C2QBVG3NHJZM08D9K7WH1D7Y9700HE4ZY8P93R0", + "3QWCF4QTQSJ46XQP2QJPSQT4SNCQPVAQ6W6XBCDHAAV99JSJNGC0" + ], + "h_age_commitment":"FJGFV1HEJNK8PD8P049Z9YXK4F4HEJ9YBEMCAJ7X0XCJN3ZJ1V20" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"J6H98A8Z02E7R6DWPHVKJEQ2HCK378V7R30T47AST38Q46TJFXR4X9X0BGA3Q8499JFVE630D0XKFDHSHKJD95T22ARVWXTDP5RB81R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"3NMT7BXRCN8MT089TQEZ9B5BZQ2B1HKDTNH86DSEXK0ZR2ETS57JJB97ZH5155881GGCYD80J0H633T7C1HZYRB2WH94H0T36QWW810" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"ZQ7MD1J259PWGF0MV1D2JE2A6QRXEWW72SHQ9ZV0X1CX00YRBXTJDPXGSMFK1D6RAXN521283RY48CY7XBY52RJH8WQ4DQ5XQRH8W30" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"NDNYY9GCEPCTPD0FY78Z5HXWWPG5AM9787BY1C51M08554VX09X6T6PDQ4D8GVZNZASH525R6Z77HC4K6XF26MJP59Q2W6RYJGW9M2R" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"1DGKGYQYB4EPZBNG086VNV2BTS4KP1A189PYK447645ZN6KA707P0RK9NHENNFE84P7QCG2GVDV9VVK5F9X90RCS6FGKF0Y0EZFZ218" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"V6WFAGBMVXY2N1A5W4EDQBM5RRZJ6GSRCRQ89ZED61D30RXXBTQ4PK3M3BVM6WCBCEBGQYX6GEK2SNSY14KKY7SP76CZ5265HTSTY18" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"B6ZKKEM7Z55YP8GNJ72BT41Y3HZE5FX67CVYRNTCJ2Z87HVZZ3Z097712J9YJ90AH0KH3HCC81QX0DKXWRD77R3VX5Y4J1A2KXQ6820" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"Z9XDTFMXRZ33Q5AXSD8MA0A8QF9H8VYTP8RMFEMYZ8NJ9C76XCQEDW24PV7ZR4610VJKC5CX1KCEDFWX1TV8HVWMAYEWDVMX8HA0410" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"8JFPM167HSA42ZHPREK7B98CM08RHMA3EX0KF9TJYPBK0HWF6EQ93SH7RTATNT6VXTQX5N7JT6V9AD9TZ2WHCN2VNZF5WGQKJF1YM38" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"X9JNVHAYYTWDDWE1MN5X46MN9WZF4DWCZYPDNH0Q00ZA51THDXM6NEFBW1KBVK6B5DNXWC5VPBA38AW2F2DT2E73JT4WWX69W1R8E20" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"4T0NKM0XSAQ96HV9E8ZK5GBKYJA1A0QX6ET6SVXZCSM21KA5WSX4BG1NR4XRFMA3J84ESJRZ76VK1QVZX92NM1WW92NTFRMRVA7NG38" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"M1JZBM9WMBVN57RWF7MNXE8VM1NSDZPJFCH42Q3NXBH7HG9VVFRA4BNR5HATYJ4KDT1D5BX4HW92AHW0W3H9N9XPZ0HQEN32VETMP10" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"AESYGKCRS7S1YJJ96JNVE8THEQ9SSCK5YN2DK5VP9758X4B3383N2117ETCV1XR9BANJFB6JFFBCGT38ZAXW2C9677Q360RC0G62R08" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"4PHDGT5S0C4JZQQ9SYMZ05C46EN3WVGPXABA8436057H5DPMYQ1RMYXZ3X7E7Y1EYN14KRJCGPVW5S7SPE2HYR0VA6M2MX4R0WN0P08" + } + ] + } + ] + }, + { + "commited_age":20, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR", + "proof":{ + "num":3, + "edx25519_privs":[ + "41DX6Y4TKW1KV57D2NMSW9EANFYNFCK0TAPF8K1P2FN6XMZFEXWHMDVT19P8PKZCZDHVFR7V7AQNJK6PBVB1GCT9MJY72WYFNW8NE4G", + "10EDCEAN85D3WTQFD7QRHA13876T5JPY63A5YY9KQPEK8MR33S9MK7NBH6THFPPXH5ERHZT8C9YP5WCQ69AYY6GQ3FHAAG8WVXW8S6G", + "03R09BWZW3G2XVSMXSDWXF6TS96VD9D4X4HN03BFQWQQ955SRDSW7720V0163A9GSG7QSS3J1NGJ5K5XAXFSQDBSDD68VWK4Y9T0FK8" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "ZBY02X0EN915A89T2DH4BZ6VNPG72K2ER2KYPBB534KK2TBSFP10", + "FJN6P698QMZKTZ73P9B2M7JN4TCN07732ZM33MZ0BD858M2BNAHG", + "QHBC3NMCQ565VQA5HR640XD2K9KFN1BA2SFWPV4PNAM51SBWDDNG" + ], + "h_age_commitment":"NHZA7N6C36K9JSAT1X0P5HJKGZSQSZA24S1PVKXB4XP22YMFKGYG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"AXWMV52MDDAEJCXTF8PPTXNKTCE5QTGF44475SR3PZSEYB9KQBZMA6BGHNJPKSABVCE9KR1C3SFGWF8GR9WFJ9TZFY0NX928S5W7G1R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"4XB7KHBFFQY7PWP5NJBTAJA6KG4GVC33N44GH7Q3W9C56D5YJAH93QED31YK2CTDADVP8H4V99W02F3R9GR6ARV09Y51HDYW2CEQR1G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"0JE7XCTDDTDR7R609EF5VV0XXKQ3E5E60YTFQ5MJ0CBAYPBTYH619FDTPKDBTNHND662QWNKYRK9N7HGJW6A7MJVK9C9S24D9MYY818" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"W588426W5Q9W7KF2AXZTPA89TE7SJA3S6TWV1AW9JM447M9P3MTJDYNXJRE6MYNGR5H3HZZJB4RK93XBYTYE0REGRJAA9D246Y5KA00" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"GYMH8KZ65KPZ99Q536MY6J7ZRQBE4BCPJH2M83K8PVVNCF66SHE62PHST3AS0H570KT08MGDFV6VG1GP68QTCMC5TP3C3A6M823V60G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"EZKD4XWW72G1ZYE3N1CE81TF914R1SG05K8Q9D3TEQZGXPAFTKH8WAGZAMC1GEQ2WQ1HHS7XQ93KXCH7DEZ5ASRTP2VT7E7PQGBMG28" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"5JMMD0EJ1SC5MXTAD8A1055FE7AZ2TVDX7W0V2RZ9DH4QDN8T2S21JQTP5KS07AAZPT8GXPT4MMZVS2SS2E03RMRPN9GDMDSAPZF83G" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"5K04VMJM1PYD84YC5ZV5F3A3V4FEBE99Y1M24769K830SV19VHPEPYYXGCTK02NXABT05B2326RJFHTR8TZBYGJBCEPKPZS06X7GA2G" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"HWEKX724J0VR0BNB6RC249VPZSZRSBYYJPKPWHSS4C3V6QPP03YKMY8QY5KGADQ4J8SM7T5DTAPT5KN6872WM5VFCGBSVEBC6QXG418" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"ZAXJ3YBVE5GXQ2XXG10KA9R5AXKYE9XB29D2K61NPXW0JJH0YBP0V7K0EDQHVPPF77GDYNN9SYEJEPJ8ZSJVDH4RSMDRTA83Z3NSC2R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"SDFW1MWTCZSK3S65DY0ZBDYAY88A2XN6D80NC9MMFGJF18TC0QEPWFN8556V71MRMH5JYZCSK3YYM7NJGVES9WXG37CM1X80A01HR0G" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"KV9DWFKAQC041SZ0CE9C34HJ4RY2344JERYA6MMRGAX7QG94ZFGK18DX7QTSGJ72MP1Q7ZQN83EEDR5E5NP9G1DE9YE5JJ52VDYT21R" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"C5984WF8VX0S6MNV7RVA703ZX9J0E4HC9TRJ836Y930R3HD6C0Q2MY1KB3BXBS51S6SW4W9W5R99SKVJNDET5YAJ0846R7VCSCZPW2G" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"5Q1RZ98RKHHAP0J6SV4XX2XKYCC147D2BA02AD6Q4HN2B35AAFXT5X2ERJD50HYPB9T3VV6JJ2BGHB752G8X47DW88EYBYR9XJRP808" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR", + "proof":{ + "num":3, + "edx25519_privs":[ + "34PVE6CJAJ7Z60QJNFYV2ARDRHY3H5H28GGDN8J12XPPAAF3TR7CE0W90SQBKVREHWDM3B585D7N4YD06QJTVG60GZJC0SB7F6H9Z20", + "MJ6TPVE0TYXF9DRA9Z7R9WJ6K9WBBSGC8EBTAK4K1Y82CWH74G5R1F0TFCPFD8BWMCTMDNNJRRX60831F6YQRYEK6GQSNN0Z2ED544G", + "V4M3WFSQRGVZPN8QB5PM794Z3T403TV0RZZX393QVAQ1AB9ASR1M5KPRGG8SMXVSJRKYCRC21HFVTVZ0DK6VHRQH40NVZ3VVFWYQXZR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "FWD7CEQCG64S76PSWH4XS2107W0SMAJKX91AAEY7ZX3A6B6754B0", + "W0ZWYCKFJ593YPAVDGNM5M6PB17K8W9SK1AQ3E1HRHKN3Q86ZDA0", + "8TWFG524ZNW1TS6J99S34BTMWZ9B2WBQTQWAVX1RM7T70Z35EXQ0" + ], + "h_age_commitment":"HYD1W8WS3Y5HCZFGZ6Y5Z55MXPSAPQQF7T15NH89STK03AN4K2M0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"KWWNZ3YP35TB5T4PR4RWVD2QV6EMK873YZ0SAJ3SYXD8DEG8ACTZ2AG0MH4FQZSWNG60E98X2ABKSYGNBAXYED2B2KKGHV1NBZB6210" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"XMZD63C8V3HRR8TES3F5TM64NCS4SEMP3TJ5R6MW3334HCWK1PZTZJZAJ3RMXD72KET67TJ50KCNKWKHN7RJ49736WKMRCVKH1E8W2R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"2J0W9Y5BWG9R7HQ5TGFY5JA7TGFJ870DZD912WH4XA5QZBFNYSF1YX075E8SMMK107E9G2G6ZK9NYTQ81RNNT62SMG0PEJFHSMPSW2G" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"FFA5REHB9MXN4QCMXCP4NZSMXMSZ57EDN67SJKY7ETJ7H2XTYYX61DZ365G8JYYWKJ6BQM38ARBSZC61ZY8FZ4VB2CVSWJ555E4D00R" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"S7JWXFKE1VXKJZEDXZZKP967G24ZF04VF7128PGFG82C1VRNQ0QEB0FEV5AC3ZB6R77H33M87FSVNQ6WHYVPRSDNAEJ1JBB1CB7SG0G" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"E0NZZKMPEMH3YYC62EAACWFE0DN5BFWWG6WPNRZFWJY3GAN5D3TF0GM77HYZGG4HD0K5RVV8872MPWMQV9K3DFX08ZS3ZTNAVFSS60G" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"T3FCVFGX9ZXQRSBWT54DN1PRDQ5P9YE2JJ5AVKJVW7D0C0NP9CVAV4CKRQED0PHCT14CY50YV8AFPX6XWA1WTNR5JJQXY436R6RV61G" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"QY37SJNPYJK3M5KH508ZV6HTBE4D2717JE3CXJMT3FH9BYK03SMEH0PEWSMDERA0VHSRZPD5NVPRK2WVJ6Y5MC8EXXWVJM4ZWFHVG1R" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"DRV6GKFV4E8W40CE20NM9WP9BGX6FH8PHKMAAAFP7SCQ3WSSKF8XWWJ016HDHT84F7JMXEHH3BX91RE00ZW2KCWKVSY0HDNEC5N8T2R" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"84DAZS060JYDCYAQ43H3GM13E2X5VFV2XRE42YNHT4DSFD9TWTZ0NPCR6RM9BMGA8D4NS0J9A29DNNPA506AVN5C6Z2H2CKZFSTEA0G" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"J0KS0JGFQ9D0C4WQDAG5HPC2D1PBQ1Q7BAE1JHH39NK7H59MWTXRDD0DGBR2FNCXW2NW1KH660CGHKEPE81F9Z73N9R9PNE4HBWD430" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"EAEKJQA4GZ7VNGGC29VXD62JBNN09VV7394CW1NHAD0VQ17PHDS4E64KFBGNRTRY29269W0GS2JQZQRZS32NDZ3MDQZ85KDA1QSMG28" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"N19BMDVWBTQ86CSE4R3244AMDCW6RZBWG07N1B6VABXF87P385ZDSZCJ744604E7TT9CCV7QR58BB2P3G3N3PMEHWE8GSNTXPHZNA38" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"YQ0VRT6D63N2550RBTQXW7H94BPWTG3JXZZQNHB9WAEDSFC2F9N4HSE3P5CTR7WGRBD6X3J07BE7V4A232E2Y6R6A6W0EX9D7ZV2R10" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"TGY14TF3402JC86F70FEM22QTAZFR0AFHZHCWERJJRM0NTQ2803GV10W8WWG061X4PJSMJXAX0VETJ4NQ7KQGMJB82P18M9TYSDVZCR", + "proof":{ + "num":3, + "edx25519_privs":[ + "FK7P5ZKNRKWC7ZCYQRY59XSBZTPBVX7DGTX59MCY22W9023VRM7TADA2SGXZJ5NPM5RJDY5PT0SM3302RRA7KC7FX4MPVDY6S80M268", + "3ZT51K598J0YJV34FJ0YXH4J25CQ39W7BA4F0BMDPYDP3T6ZP8221VKSWMEK051DPZZ1S75G6EFZDRBGZ1EK9KFS038365W1R51BKGR", + "0M46EQHXWX49TNA49M0TN985S6BG8H47474QY18DM19SB6NNGG7S3D7GBT87KTJBVK8FBAV8X7CF1ZEHET1W7TS7CXAQKM1S0Q20TQ0" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "30FRMN0GDFYRES703Q185GF86SNKNK0GTW4XVX2CWQW94A3M51D0", + "DESCJ2S5542QF05XE4YRH3R0QD31HHMPX27440KDJVBGYE4Y5TN0", + "3BPERHQMSHSP32VKJGP3ANVVME6Q0BW5B8FNAWW0SZGDMYDP9JT0" + ], + "h_age_commitment":"VMWVB3RWJWD0A1EW006N6N39VMWJ49433CS9BG7RVTJMHT2H2TPG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"A3K7ZFPYG7YJMZRWSP1MBBT28Q9XZ88PNFKNZ6BV88Y5MD0XYX4447402RYPRCQVQ2W83Z2YVAFXED698TS8HAHNA9BPDQ44Y6XTJ3R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"JFTKX6Z0BFK9CBEY8006SM0ERKKMKFC7N5EMXX3JA0C999GFVVMHX85RV1TR807QXJZ4BRAVM895YVMQ5GFAKGA14X1M0FXVHHES42G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"K05V1WRT1A7DHTMQHY9NGZTR5DEP716ETEDRBEX1EH7K8VQ4HCPNSC19T6Z354J23CAAAJA47R5WPASZ1MWVZASDX0WQJYT92PBSY2R" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"EB4H7N476SN5ZTY27WHWYTBCPVKZDQXEJEN12Y9PY6GC8Q5SNDWT3R3MEQW1AE3AW2F27ZXZNF7KPEAW39MDPS9NBSPCVYHFZBJG00G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"QKQSSB38PQXT32F66D2NT6WB18D7HKDSDVVG3ZB4Z9FFR2VWB2WNMAMRJVEF1M8WXF70RRZZ02S8VRATAQTS4XG3F3DWP4NMMN60E00" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"8SPB2R1ASBGC6Q3ZZBZ3JATYDSS01HRNDQCEPZ8C2EB4NTGFYHT8SSQ7NKY7KN8DN6PHMPJTR54VSTTREF1A5ZFW7WKBFFBDH9ZJA28" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"V4969YHWKE79TYZ2FFPKM9X5HBFH1M78M21MY14FWQTRS3NGXPH7CJHNVDPTC1QFEPNZ9QZBTRW79QJK0H49E1YXAR4D717R9EXE608" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"0GDP4XN7MC1T745KJTS29361562XYADRCG27E54X8DE9VY0Y3KWEA5QJKPMKSTATWKY8BGHFTP0DH5WR98ENWP9QS56RC5HG6GP923R" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"X9C77MXG7KYSYBK2DF9Q7Z2A6N6R80RSC6TZHYC0HC87JTN1GJE0DF7VDYP3HWXM8YP13H7PCMQXB77P4AHZRNGEQ4D2YYBHZDJ3P10" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"3A9B2DAR0TYGWSRP9TZRGHYNZGFJFAWKVS4GRW2ZE0ZTS5WN7SW2CK7671TYF9YBTAJMREXGX6M38VZRYQFY0NQR1Z5QZHSJ97H5P2G" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"5WR6XY3G67YJW9ZV8SSCG67WPTMNJTTW1TVZJ1D3NCPQK3JRRG14GN40RW15XYCWEG158YDF98DQQSPTE2NKEJN3ACNHJ4NVXG1W208" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"M7MJDD9FYDGT5PDV6B16V42WYGF1V3DAQTXQSV9JFA9K0JPVDM1Z3FK3N9ES2H8G476TVD5H1WXVQQR0T461MYSGFY9EHE9E8YMV000" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"242XMD04EQ0PQ5K9V78N67YA6Q59M1EGPEF4111W0ZZK4FAEBGMB0BH83ZZ2AAS5BKB2PHGWM6YYA6T7MEVPWJQEK9ACEN97BS4PE3G" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"ASKEM60PG6E14BYYEK93RW50BYQ8ZM8H3KHB48TF5K4BAYKXBYHT8F8C8ZMZEVS1HXXX8A7X5A7Q49438E8E3ATE4JJXH1FSV9BH81R" + } + ] + } + ] + }, + { + "commited_age":22, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG", + "proof":{ + "num":3, + "edx25519_privs":[ + "C3F9BA1BYZDFN4SJ9NW4CYG0EWNZRTD9186SDQZCYC11DSQ8PXGYECQHHEQV8CENF8WJKSAH8ZH7AFCKC35GP70AM531AC2Y9F0JYAR", + "V0V1H4BGS2EZ11M142A0XR44G517MJ15FT8MC3ZYCNF0H17ZPXKFMAJ4DVZVMBXF9RWZ98P848X8QP5E49MCEDRTDW98N80QDAEVS0G", + "H0WKAPWW3QX53ZH2NXV40YTSEPZN88NBBTT7HTWD72YGVK6SV58RH8G9Z2CXJ5WAFQ0A7ERAQ0A06CPVD7JTS5SZBREMAKYFC4B2AZR" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "T11W62FGSEY2F4XK9PRVNCAA7AMSYTVJZ7461A88GYWT1GS2VSCG", + "H7BGRC2EVKBV4KJQ7RBS241Y0G8R2V1PF7ZZF204B7N5GVP23VPG", + "36BGED6KGQTVG7MJ1ZRX5V4VN99JY09RVNMK72JAS2H3SK8ZC8B0" + ], + "h_age_commitment":"S66G4FWPD3FJ29WX4458ZSDAZCZ3Z7B54BJH916Y1WBBJHBB7G30" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"0W9X82GDB3RB5RNZC4Y0V458X1V8TY3AWEN32QDRS8KHX0DSKPGXHY1KBXZ3FZB3PB9NG1HV36PVYV87EH59RC7CFR1V05VG93PQJ3R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"CPAW0J5F7RXT4PKGKR0D20XY081HNYZNJR5DJDN5YAPHSZMQ304YQGP9TPJ3VN7RSSWYC7TC4ES4JBZ9G2Y1BZ7W88JYHSP4G4TZR28" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"ZQTH3BS8EJAV6EPGETH0T1QDNKHSPRQ0S7W0MSBQY1RBA3KMF5F644J2GEGJZY9GNKMKABJT8JBMET30BVNTDP48XDZGK3RS2PCZ01R" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"QKM5M2HSWZ2ZV5H4AS33Y8ZFH1R8YKD9ZWBH4FNBDRK7N535JMMZVTWBKTG48FJWF6JR35HXJDFB6J33D3ATDE876PY81SZQPHT3G00" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"50JFZ5SHH7H7R6EJJ5C0FQ2CGF1TX0NN4EQZWXZGJ18JY8D1VD2BPG6J7ZXVHRJHSRR6GV3EXJFCGF35568BV0MWH5M1DF1TP2AAM0R" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"YHZWASPEPA64WBMEZ4QY2WPECQ8N069NDCNN281N20Q31JMF4626XQWBXY6S4P2BF5D8ND5KCM1273WS638WW1A1QGQ38T9XRYR4C3R" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"NRM8C22S3S3NF2GBPXC6Y9JMV9BYDJK3FAV3ZB7NWYT0Q9N8VTG262W13P2R3YYQ7WNNP261VAQD2Y3BWJ2MMWATT4S1ET9QW1QSY38" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"S7G11MNEETM67XFDJ3VAWJ5HF32TXV0ABGEBA6NGYAWKRSXKH2QRDM0BC9F3TDAV4BJWSYTX13AEYR7RJKQ08FA8VPHAK6GYHG5WT3R" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"8AC02VG8BX32ZDFMX4VTESKR3DQDQGNMC6BBNJK91T9YDCNJ5MBPB14PRAWPACGC3M8GHN6DGT9XQHM34X0Y3YF49SJ7TCHZ19YHC30" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"PR96ZNZQ2WP9H1XK45TKX2K5Z6Z37Z30M4KN721CDDDZQB1T3YWXN0DVBFK5429Q8CD9MSZS4TG2EE1AH0H8SGEJW4XD0YF50DGKJ10" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"10YB0S3J2G2YGCXHT0559ZC7YFG48G76E2PC65FN49N5MKWAQCCF2ZSZEY9N7FR3BY0H5NV2JTS8M21YQZEA0EBN3MGSRPR04PSFG30" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"5KEG14FVHFSSHD383QQB05T8GNDKNYY1FVMAMTTM5KBR0VHP8970M9Z8QH5G6RK8SJMHME8GEV14CE6VS4WWKZJC93F9DD5S2W5PR20" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"WYGR281TK2TXPJE19CT170QM86RYZ3MDWQ66228NEZ9EBGF5F8R9W5Z1X6DBGA3BAJGXDM6DCQMZNAJG9WPCJKCV81RD6S4Q9P0YT38" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"GSVVRGA8D4NKAES6BEK40S3ZJRGQXXQVZJAVJVTX0KBJ9S55YHH9TWPAN4M78WHM7RJGF8157CBSGRKGDPXZNN42YSSZTD0678QZT30" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG", + "proof":{ + "num":3, + "edx25519_privs":[ + "20NQ0RZ20XTPVN0VB1A56W80BPBTWR1TW9M7YBZJB60P3VAQE83Z6DK2J73WR1MBWQB0Y0FPFSGNZA87W7HVEMGN59EHHQVAJ8P09W0", + "21HZKM0VHPKQRZ39BEJW9K80BQ2ZYRY00HW737C9RH9864PBVM0BQFJAVNTTBR8TF3MT274SVJ83G8AECPXGANKGA70GJ5E1AWBKAW0", + "8TR4H3K2ED1MAH97YGMTX2MMPHMG6FSH7BF9A819AW0RYXSYVW7A24B61JAJ15NV1G94KBBKK53HF047NK8KFAJ8WZNFACF64QDCZ0G" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "SXQYBN08JTY6SPZGS54A0QK3GTBTXK1T8BA0KDH9Y36GNP5B1SS0", + "ZRY20JYDS73A6BG0Z3ARP1NED9HWHCGDV9ZEAC5T0GYF5CKPRZ7G", + "CGZFCJ71ZF1R68RXECTN2YHVC4VWDAAEJH4YGXJWPAKF6K1EDJXG" + ], + "h_age_commitment":"XXQ1AV54BB0KKP7X11V4FFZMACNHHYA08NMH1SA1RXFW1E8HFP70" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"CVHD002WCFC3V34FE69A7YYWDXFEGH8A8Y93P365ZJEJ3E9YZ5BGWVB6CKPMN2HNN1CAV9RD3SM2HTBCKV0WAKKC4TFJ7K5H61AW420" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"F2WM49F1RW481GAAGBKCQ9YSGGFX2EPY24NS5P97HFNMF60Q4XD7R894Z8QYMV2VC0SWWST2HACWGQ0R9YHC6AJD4AG1ZKJ3PTTKC3G" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"K50JN18WR7JRMSWAM9AGZT2HVS03JZ0C35TXRTGE5CH2C97DJCGX6Q02AK4JWPYS2TPTPXTJQKZB43DZY2FCM6QYMH9FQ6ND7CSKM38" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"5NXQ8Z14Z8F458HZZXJ00QTKCCTC2EHG8ZE3KYFMK28M6BJ6GEGPVTM7VRVPG1SE6V80Y6G50D38K7344QPK9PVRCFTMWPADP5X0J3G" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"K51RSG6MSVBVVVZG1ZHTKQ3RSBGP13BJS76ZEF64BH3XWWHB26YKBENNX5F57SY1694N01ANFGQSBD1V7VDX8H8W0D2N3M7SHGFBW30" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"C3DGBN2VNQ1B3JTPKVHDG0VFEZ60GPGZ9S4J68QW2PATY30XBME694GP00KY42FTFENRTEGQZS08DCXVV9MVXC9WVTW61DQJM1TJG08" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"4SWDRCWAQ5R5JWQ9ARKW7KJWS1MHY1AJN120V2VRJESY8SRPDBJV99TXKPKGWFHZ5MFRSCJ0WZHXZJT4HGFZVDG75QAKSCJC20J6R3G" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"ZW5SB33C2EA6Y41Z30TJWBPP8CT929YMBA7H5B13HABVYEQPWBT57JRRVCRK9CEH6J1SCNE2K9RJ78WN5ECE7DA7X3ZYN6P2D277W30" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"GFANXH6ZN79ZDEA8Z95VHXDW020N6N88KX37JA3YGZSABKJSKNNPWSKX8ZZFSKPB9F0WV4YXGVKVS6SQ2V4WK56HAT67YR65CK2T83G" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"3S9PFZ2FKYEGJDENDJFDGNKE1VV4XYY0HP46D95WY63YQ08Z5WVTAZ6X77JQ6DGRGXMV1CZX2165M6F72TESN0MPN3QFFVZNYZNM41R" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"DVFJ3G165T8NYMWF77K32N6WKJN1TYZH0CPZKVQNSFYVRMT37BZG9QP1KA0PKDTC378FN2G0Q6MP8Y510WF28K30FWC68BQNG28CT3R" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"MS1AVQNAXJ5H6Q6H69SWPS6M0PM4WGSHVVSQDTG8AWBXPG5MJS84H50EFXV2DWFJ57F6WJS04YKRPTYS5DRPA3S2P5J2RMWAB1PWM00" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"Q65SMPYCJRBYR5J75MT9K9AAM70413DAT08TEEXKAZCEDGFSBR36KEWC280FKYP37DTPHRXNCDEMFV0MQ8MGMY05RCTN6ANJRM3MC38" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"BYKA65HDVF7ER7V8JC286FRNVBWX4SHDM90MPB68T4M49TFR49ERGZ0A6SZSJXSM5Z6SFXR3CXD9X4F12245DMH8SZ5T880B28EFM30" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"WD9WBM5P1HKXJF22TCX5B7MBGBTNT679QCV59A9JQS4ZPT2KV516HV3H74GH2K6989W8JGFTZE0A8E31W95RFJD65DTH0HPPSD1V3XG", + "proof":{ + "num":3, + "edx25519_privs":[ + "EQCM855500YTHRR0C5XFTG4ZCYV609QP9EHTEH6R312H6TNCN07NRQREKG386XEAPTH1QJDQ84FGGXC4B9HMZX9MZYB13ZHMVHZ4NAR", + "W61VPH5B1GA629Q3A19X84GYVWWCAETVGZH82WHGJ0KSP11DCG4NR92WB4KVJ47FCK326SC64N5HEDKTZ0VQ85FK51SY16W13X3WR90", + "MHR2R2BNRKVQH38KHSZ98SDGR7BN1ZF1W08MWWRSM4980VBG243BDDG1YZ7MTH6MTJ49QHWS4B70BDRF88NXW5S4ZW8F3QWN4W6PQ1R" + ] + }, + "commitment":{ + "num":3, + "edx25519_pubs":[ + "GYEXK6M5ZS2WS9E6WRTQKMFM2PFG43SXA165F2WRPMV0PA281A20", + "XTJ2YQ6EYW6QZ6R4DV51D6DV09JXBRCWYX7FVVYZ781GE1ACHC70", + "TEFATTA4NRVZYRCA12D4G73K3NHZMVGVQ8SNZ02YZJF7QGX9MW5G" + ], + "h_age_commitment":"8H6Y18ST3RNSQ6EESSY9A4X5MY442976FBYQD3X1Z7ZYNMRK691G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"8MX0R1JH18RECXBWGNQ1E3M8BNMWS35PWRCFPKDSC0XQNQHEXYH39356A2WXKGHK899MBQCH9NB3QTPCD7W747S4KS7VTSAQ5KVSR30" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"R8HJ5S9B3GMMKF4H7Z1MNHW7X79BKHA4J4JQPS5DJ5RBY76Q51Y31VNRDSW3104VRK93VQT4CD9X531S1YBZ6X5WGVJR0SEN2KR9A3R" + }, + { + "required_minimum_age":10, + "calculated_age_group":1, + "attestation":"V70GRF5EKW6N0CZQ9G0T86VQHWSJEZ5M5DQTHERKYA0WBKY41VESR44YTDT2RXCWB615F1GY18FSH49P9QBDNEC42DFSG1TSXQ36628" + }, + { + "required_minimum_age":11, + "calculated_age_group":1, + "attestation":"XYRK854945R08935T444NS9YJCWXBQ5V07P6NX44242NE8BSC3P4XPMYSP9CN9GVXE1KBRE128N30WC8EHHD4BCWHRGABJG8C1T2Y30" + }, + { + "required_minimum_age":12, + "calculated_age_group":1, + "attestation":"CNBP590TKVKFGQ3JK8Z91529VATNF220FBSFSYDKP8SZE674YEFRQDZHGMNFEEMR78CN8BR27E93SX5NYE9CGKYFD0N9Z75Y60MMA18" + }, + { + "required_minimum_age":13, + "calculated_age_group":1, + "attestation":"N31KF8M09X319QH6DHY7YY0YV1QNZS7A1ZQ85EPKGNN2F0DRMJP6652V08KZA7B3XXN02BMW60P0F6AVWY9RKC7HEJSY68ZN7NPB600" + }, + { + "required_minimum_age":14, + "calculated_age_group":2, + "attestation":"VZSZ1RWJ6E15FS0CCHMNA7BCEX6FDSA6WA2CPRZ3YM14SVNR3PGX7SMFP4QN64QHFDRSGKM5FZ1NC4XSZCXZA9VEEFGQNGPYMWSAE38" + }, + { + "required_minimum_age":15, + "calculated_age_group":2, + "attestation":"M65ZYXFRWT39KGP30Y6EPF4MX49446DHFWNAP57P52TS7J91852VFX60TJAR0BKB5T43AYPZXQW55QWTQR5YJKS7XCFE52JF27AQ630" + }, + { + "required_minimum_age":16, + "calculated_age_group":2, + "attestation":"3YJSY3KTV6TV4E0T1NSHG535Y6HHQVFQBM664ASS96YKCZ1PG818YRER653TM2CZVWFAJPBBGNBVBJ28AACWBDTVKMA0TEAQMBV021R" + }, + { + "required_minimum_age":17, + "calculated_age_group":2, + "attestation":"8EQJB360DRFBMWHZX6BDH6E9DTKS1DSG2WP95DGCFRK6JMPMZFM8K791R54BZ980P5BVKDJ36128SZFEA4PXNPAVN3WHTFK9YX7T838" + }, + { + "required_minimum_age":18, + "calculated_age_group":3, + "attestation":"1GECW5055YMFJTHWXFDKCDB9VCZP256FA75KF7V4V1SX6AJDAV34GD0VCMT9G1J1PYS1VWG6Z892GP98JS5C10TGN5E9RM3J96HJC2G" + }, + { + "required_minimum_age":19, + "calculated_age_group":3, + "attestation":"1XAKQ15WS0BKCH75XBRDK11MCZ1CY9AFY86F4512YPPQQ0WY613BN96XJDFA68T3Y7ZZ93YYECRVJA7GJAXDND9S8B0PXKY1Z5XWE30" + }, + { + "required_minimum_age":20, + "calculated_age_group":3, + "attestation":"XD6FCYQJNSKXC0DJFNNPWKCTEX6DFAJQMNGNCS9CH9K8ZBABYC09Y56QJSPHQCWN983Z2EYT2DH8TEEF01W2BMQ9G7VYYJM6PR28J30" + }, + { + "required_minimum_age":21, + "calculated_age_group":3, + "attestation":"7NGCT3MMDED9QYY5FGR0YQNA0S6VG8JG7D3ZWWKDW2BFGH060R9N12SJ2DZ4A0CWZYXP4KDKBK0C18BTXKBD7RPXGAFKEWD7S8QT228" + } + ] + } + ] + } + ] + }, + { + "age_groups":"8:10:12:14:16:18:21", + "age_mask":2446593, + "test_data":[ + { + "commited_age":0, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "BJ587BYMWE9P33GDJ4EYHEWFJSY0NYGR1X18SFAZ6M2FMHKSYZM0", + "ND97NHZ10BTAWPQBEBF5KSJHWX5VGYK4Y509P9ZADBM5F6W0YGVG", + "3TS8W0RNJ0ZRD94N4NHTAG617QWK3MGMP8YRFX2YW1XP98001YDG", + "95QPEEQ3WK14TC0XRZCXN4BF28TB3Q59GQY5M7ZBP90EHQ7EGWP0", + "90MEC4AHC0D7REW8RWSZJW2WE2T3SHP67N4KZF93EMV5P6JKETBG", + "XH1FSKVQBXYDCXH7XHJJAN0C84YNAB9WKPQ2DSEDS74H9WX1PC4G", + "AWMER3A227A7W6M65BJ1C42KSKBKKPZ3ST1CBY19FBR7K6KN6C10" + ], + "h_age_commitment":"XS27QG909GK9JZTWJTAP926RWBWRVXYPAW5G50KVCQJ45ZY3PF60" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "G5QXTS9P0K4CBQF5KAJT7T01W67PXG1YDD7TT8MBNGJVF9Z9C8MG", + "5BNH9E2CNHXJGH6QXN2Z7XN9RFW6PBF32QSEZ5EKTMZ7XFW87C2G", + "0MHNRQ43DG8B53A273PXD9CC26XYDFEFYTT7NNER728FXK9V9EF0", + "AEH8BMNYCXE6ASPBQMQDWF0XZCGJ7M7YGGDWYPVXA88V5TJSQBZ0", + "PMCPTK64QV4Q4MG0FZ2DNFFJCDD2VCD5W68R1AJFACZN1B4E1940", + "EW213CHQWC690Y3J90XQ76MBTQYXJAQARW2SX43MWZ7VW7WW43F0", + "HMD8J43VZRDAWE3XRJDK640V5CQPVG827Y348JKEDXHC2YRR0N0G" + ], + "h_age_commitment":"A92BD3930YASQJWKX5ZGS8AC8191YV13QNRNFVMDWVNT2N4MN6CG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"33KPMQZ5E2DH4P564AW2KQVT3SCCFXR38Z8ZJASGEPV7N7BT51J0BHKWT5QVTXVMK6545949ZREVD5E7GMJZH5FAJ5A8F5X5JTWY8BR", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "QV0C9GHP7HNG02M026G06Y82YZ8TFDK835Q17JYV3S2CQY62WDM0", + "N9W0A24QA8XP01W8WWKN4KVGCERHCFKT4MTJJTFA9ZNQY67F33P0", + "PK6T07FEP5NH2PDVWMQC3GB59XY2PN0TSSPXZNN3Z7RY1E6DQT9G", + "BB4NMS25NMKQNSMTSWM4JV2VY8EFPTGPV6KZEFY32TV4ZR35QCW0", + "MKF80QZ1AKK443ANW1H2XAV3NKZV1H51XSBN0CS54SMD6AK4RHRG", + "942XJVWCWV1X93C0RX9CF6NHH0746H4TDKN3EKT7DQTNRDXV24YG", + "THC6SBYSWBZXK0HM50GQHRC5QVVXK49GC1626FNXP4G1A8B2YGY0" + ], + "h_age_commitment":"K9SWMQRFB55GKNXFEQ5QJ7YQ3T2PPTKTRKD6D7E3ZX4C4B5R1NMG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":2, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "A4EN82TNVQD3T1W3P0V4K2SYQMEYTKXHX6Z7N8SM4JJQ8N0AVG3G", + "Z0MDPXN40R6DTYFJJH7MX1JHR9PAQ515RAD1TP2FVHW8N442CF10", + "P0JEBVN6653X7EFZ2E30X7W0T8WHDHSKJ62G3QJVN9BE7B2DVAMG", + "VWZZ8PJFK02AVD3RW1PJQ4N9CHX8D8AP6PDZAMTSYBP1713H965G", + "NPQS2A0VFYKGBVCG8NDMENKN71ZER6W76DFA1YZ8CCQBYJM2S7DG", + "9JBMSGDJ7NW1QC4WZT7XWWR0E6VFWTTKNR0GA81H8C0XX3YSQMC0", + "A0T9BRP13P74A2W8Q62JS0N8JYB5718M2DXNA9NMMR5WXZEKRQC0" + ], + "h_age_commitment":"S1YWS0RTWKXH2J2N6RTY5C8K2S90NKDNFDHYT8YFEGYVV3T74QHG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "TYV8X7TK73DHJVFVKR8GFC369MZFESQPXN5MY431MPFAZHPD40H0", + "1EZ64RT9ERZRG0KPS7BD14GBX5CD9D4BD0T8XFJEG6WE3ZAHA5TG", + "YMK1NQYG4A4Z1A7AFXVGPDC6G18G05J76RV3AR6WBYA0MRSGQJB0", + "71F64MKMT1HJ7PER43KYBHR2K7H39HBP9JY5D6MQ27WDCG7PKHD0", + "6DVTSKWJWZ4HWXJDWYTTS68RVDJ8GWHYPYRS45R1F7369G4ZQRD0", + "7JD5BMDF95SRSP2HQFGZG3E4D9499RH166RM6JPPGN33SCPKW4ZG", + "BSAFHS43JNG0NW4R6CJE62N7QFQK9NRC4R841E8B5M5GD2T7W2N0" + ], + "h_age_commitment":"P6457PP9SAA9E12RJ75Q9FE1C1YK0F601FRFHCXAFG1ANRZJAEVG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"BWDYZY9B7MJPW7C5487MF4RD6QTTKXXD7N31C4TWNPZDZJS8QNP6PVHAA6QP38QXACS8XY74DHRE0SQ8CACJG4RPHQM2Y7JYTZMGB40", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "PJC09XQF8Z5MJ2CXKFGDS7CW5Z1MF1HE28PK49N8876546V3710G", + "F473F52RXZP45042ZJGTTPE9HZV78SJBVJT1G3AWAQR15SBHR8X0", + "505FMG74H23DGT2RE671R79ZCKZTRAEHD4NPR2CBQYCN2C6CB8GG", + "DHBCN2H8R9AHV9QVGRYCFA8HVPRS0MACFVP35E5HMJP417NED2AG", + "PAABR7P4CT8XJGM214EN8K5GVDN44MP9VW6XXVX8N9BZK2PAZX8G", + "GMV8VMJG468W2W3M1BB0QQE38FZ3BE8ZC95543B5KTJS2B6RM4F0", + "69ZSK1EM9W09Y1T1HPE0FGZ611Z30GKEDAC1Z0DSD82BZPES8GC0" + ], + "h_age_commitment":"05DB864KDHAX3PF9CADSTWRXYAK0Q71YWC9D4BK8WMQNDHN77PHG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":4, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "19FTR1JPCS80PAD0NHDVA3HVKQE9545SNBAT7XYRQDGRX2X9CDJG", + "CCG9654CCQNT5N2YPFEVQ0RFEEB3PY7GY1G4R9685QV91KBNWSKG", + "YWBEGJNQADMWSGEW19TFAACV5P58EEXNRQZ52H7BTCQWQ5N3TW1G", + "AKEATDEHS9F6HJSB0FAR8GRGYSQXDYEH16E4JWB519T47Q59FAKG", + "BMW1ZEDSMDAGP2X8B9MSWZYYSRFK07VW2VJ8DJH3EA5C5BCV3JKG", + "Y98EZYDFC5MGYJ1PK9YHF8XTWPBB60B61SMZXYBS04B4K1SSYZS0", + "93218MJH0RJA80NK635N7AJ329928AM8H4TST96W3GNXMZ8JBX70" + ], + "h_age_commitment":"PA2HFSYH4EEYMPR3QKKDJF6SHMD4AGFFX3WMQ3NRBQBCESN3K4DG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "EVTE01Y54CTJY9PQNQD3MKB9AFQAGPMG8KFB8HJ2DBTYWCA0M190", + "N5PB3F186H55KBCTVH5REFPGP38MA8949F79V14GTR4TAV2MZXG0", + "Y9DKPJDKCY18P3SGKB9HR51JP94E50S93T6WTM931E4CAW0GKRTG", + "JPZ2H4Q6EXBCXGC38KMVGJ8QWR1QN27H0DHQ1ZCX1TRKNZPS00G0", + "F61CMCAMZK16RSFXJQDW3R1FGR0C98GZS48DJYHPGAXE4DJFQ40G", + "6DTT3GVX2TYHR4M7VZ9C7Z8K8SC8SKZ8MN9CAX1ATS9XYRZ1NEWG", + "4YA2EXW06NQPWF1YZKH140WCJBB6RCWFY8P4DHDPTT3BXPZ56JDG" + ], + "h_age_commitment":"QPES8PQJPJJ1A0BW48HS5WWGT59TZRSX4WEXFT3D5D9VZQ1V7YZG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"07ZPG7GTHJWE9SEQYXS4THKN8Y8PFKB8PT6RS9NG6ABKNH14MC7052FV8B6MA33V49H3NF0K1BBF93GPHF2HFPSJ8HTZ7A48PSHH7D0", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "SB2EY21NTWM17A8X64JEVJYYZ98KDENW6CRV2DJTDPWBEPQ7WEX0", + "B882DG7R86PSYTYK81NYZJC8GSDEZBY72F3VJX80XC481NMWDDC0", + "ZDT45ETN84X3VZX6F6NSAJYSNTM8ZNZTT89JGRPECP2K7D4W5GEG", + "3EMJ2GWTAVHJV6N973C2MPTZH2BNF2EBGYG1XX672YBNQB4W9JYG", + "KGZ9T9WX3XQYB6EVM7M8RVWG2F8Q6E6NCPBNYVZ0ESVQKE9435SG", + "DFRS4B46DDGX5DAJQ4YXC76GF35WR9SMEDHVWK8ZVX2Z02H424KG", + "7825CS96XQGA4ZAJPBC2YYVSHMC68171G69ABSRXC8PQREPXVVTG" + ], + "h_age_commitment":"4RHCGVTQG1BNSEXZA7BXMGAS03VXCZWDJYYWCMAS35J0XWD38WE0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":6, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "ENFBQ16MEYB623CA6QN30KE73916NWB697Z1Y3QC7E0S4R277KJ0", + "NGZ2GKT4GG0EY8FP9DWFBG8M02QJGM009GX6P7MD466D3K37GD8G", + "48K4GPDYB3ZTFFXRBPCBXW5PDAPSPBXWC6G6YP58MD2PFZ334A2G", + "WJY5EFH92BZKVJZC6E5T8K1DT9JSSG9HQSTR9BZ9AHAF16KFB1SG", + "59NS9XE6E89F54QW80DZBDTYE5YJ3TJE8JFF4059FMRMA7ES09JG", + "PPEFR3EPQR6YSS2F27NHXWK2EMNNSZGGSGA6S3NVJY9N2HB3F6D0", + "HY81BRTJFGF48EE0BWZ94AVHY7PE02V1AMX5W0VG4ZGWZFHAG7AG" + ], + "h_age_commitment":"36G95Q9F3DQ73C5PQ7KPYK2SGEQ70729WZ4ZVSBX3FWG266F2TP0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "TH8NTWCQJ5GKFHBBPSH51CFGQV8XH5FAVKHDVFPR4BD5DM09MYV0", + "2WVF54F4QX9HAZ2GXP923K1ZQ1THNFCAKXJ6VHQR3A9CS2TZE8Y0", + "B0C6NQ7RBXG6D517JWPNN5RJ97R2BBESZYFVQ00HDBEZ9Y2N97Z0", + "0EKMSJNM5DAD06H4ESSB576TCDPA7B7Y6518B2A8KWQYHGWFJHEG", + "W9J4VTKSR5PX1M2DCDGH5SG3ANAVDHFRQ91NPCYHB5MNVHMWMEJ0", + "H1EZAVASR33QGW8RKNJ5CQ2RDJKDCQRREVN9D79PCYD01HEYK7P0", + "H2KA5SJQ5VYQXK79D8WH9NADHP4H8FR5HFHMF4G5VT9ZJ80FWFXG" + ], + "h_age_commitment":"Z05RV0VRMQT290YRDY2Z6ZW42DVMHGA34BYKTV7DJ4JFGAGXZ980" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"NKT41EGZS3TNAKWGDWR8J9AGGMVA2K9DK7TZZ5EM1NRVCFZQ2MFTDWBARKR86PVY0XJMHXS21A4Y9E8K7V7TDD37Q78DD4C6PDDS8XG", + "proof":{ + "num":0, + "edx25519_privs":[] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "CW3J574VWQ66FX83G62G773X33YVRJ96SSYPC6AS02042YVWRB9G", + "M1YJWDEYC9QKNG8VBSBGTBNDRAKRSMD0MWM4DSE134BMGRBBH3MG", + "V4M86WKWA1X8GNAM34A7RNCTHSWSWB670ZGZRB9CC5941WNCXC80", + "PCSH3VN3BRGEN28718TD1EC23JDARSX7YKJNG6BAXY1N8SVWT3WG", + "1RDEX48G1S5BV5PMFCGH3GEJ9SFP66NMQK2KSK6RJWY00Q8FVPMG", + "ZVQVNJRTGE4Q8DJ76H57C6G7ZBTVSZMXZSY6ZDPBZMVEM49KJNSG", + "1XN6TAFHVA5BJJ8T9T29AFB19VZ9Y1GZB52E3Q7TYKWEHSDERRP0" + ], + "h_age_commitment":"EYT6JGABFA86RT1KK1AFSVXDNADA7JSWE4K0MRPR0VQ3GTF97YT0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":8, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8", + "proof":{ + "num":1, + "edx25519_privs":[ + "C29ZEVSA91E8380JBQNKNGVBG3077M9PFXJN127X1HXZV6G4KD17T70C88T3JJ0A2MSPAK80P5PXMJH18CN0AS9YNCGEPPGQJTDXDVG" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "TJR48M7K3Z98950XPV6NCBJ5VFM3FGB32MY9R55VAS2M0ZMDPAD0", + "YE0572R01NAVY71VKSQZ1KZPZRP7K286ZDHKSM4G70ADZGMEHWN0", + "X94YD6PMA5G82HKJA6BGK60CX23QC8BRCVCJT0KDAH34Q1MPTVWG", + "J0E76PEA6CNKRNTQNP37D3EMP2SBVPGK4CDMPRTEM60TBQG03DGG", + "Q79HZQPKYRYSY9CRKS689RZDC68WGA07P4GBYDC567YH0F5381KG", + "GJ5TFKV4DMY3AEZ07EGJAAF677X45ZQD938BJCCGP60Q72VJ9PBG", + "AD5Y6F3M0JPWDH7G84THTZT3B1GFBVGN3WEFA53YDQNVRZ24ZW9G" + ], + "h_age_commitment":"BDV9SAMKDVZG5TSJX4CYARGWSD14TJ2DSKBPZM2ZNCDXFJ3EZKM0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"XW9E1SN81JJFMJXDSA4X923PVHK8Z8ZAXRD0HFSWZYNY043N7X7BFYBP42NCV31E4E22CG04P7SK3TY55HT1S64ETJXNDKCETHZ7E0R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"QTJPFSQ115P0CGVZTPQ6PC47NNXA38S3Y09S320RE5MCH4GT921Y11BTK3X30EZ2NWCJ8HRF9QVY1RJ9FYJB2XCBKWZ9NFYEXMVPP30" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8", + "proof":{ + "num":1, + "edx25519_privs":[ + "1KQ84V85VQPY6320K6E3VB12S9JQZ5NMBHGW454X8ACZ2ZG2FG1WFZXC6R2BYVGWKCB19C6J8HMSX1R1H8Q2GJH4ST0WHFZ84RPNHJ8" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "X2A967YFAEXFVX8T1PN7KSGXZX83AE7TCJJX8RHMZ3S15RKS5650", + "JKCNYQHHQ54F9X89FTWEPCN6MX52D5TFN1QJRT772KSQDKQSM550", + "DZJJ7CW6AX8NP0EVMB3476CJ380P0YN3XGQC2SKJNSF563QBZR50", + "PP1PGN9WA7DJC2RA4A3103FZ9QVG9SDDFA707APPRRTPBJAKFS00", + "1DRSCJ0FBXHQ979MT9K5DH11WC6X769M2GH3YHEQFM66B0ND79XG", + "KB2KHCCD75SX9CWCT8WGE7S626D1HHPPSZ6BMK1D9PSYJRR9MGFG", + "QXSF6NJCA55VE7MDG7PSCADBWRX4D3KDY4CT5SCH6M45RM7N6Y5G" + ], + "h_age_commitment":"N6ST2SHKPC8RCQ58AB38V8XMVAKSNV0N6BTXQQCSK0NJXZXEJ7D0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"EVK3910BKHXC6SSD3JJ4T88JHN5K6NF25XYBGTAK5YJAMDTZ86X4N1T9CZTDWSHK6D1R7KY92MZM78T6CWXPAH4Z6YDDQS2RM2BA63R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"SFP46WT0T18GZBWYXBD1YHV5JSR0P5PNG2YMX4JPTYAS9R3V6DT5G6FG7FRW1HJX2HPFT5A0Q45PX9V09TE1C1R8F8VKWYJ9T876610" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"WE2XX945BXNF74N69GR0RWFNGTR2JZ2SXGGVQS7WY5MX0YWM076WZDSCTX18FPRY6ZX3YRZ6T5WN2VAH8H1G69MJ5H6MAZM8STP84M8", + "proof":{ + "num":1, + "edx25519_privs":[ + "GVV9G6Z91P8A4S0KTHZBN1EGY08AS2GMJ4FJ0XKFKNKKVWESXG3F011MRH8E95JR7YB08WS7E5NWF6Q6XK5A5BZKFEHG0P06EK7JK00" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "KKRDTMDZRP8JSFEYRWT1VMTR7RCMKGMFN0H08G3EP8RQV3RPXTC0", + "10KPVT9JXAS42FG4Q2T4AQMXDYZSHNYVA61EQW8P7TY3DXAJAWN0", + "917QHWPAXPQ0AJ42PSG2G5NH6P370AXFYXCXHXDCWWW030K9F090", + "S87K8AZ12XXA2D6N0FSJT6PW5QCXJ6KNRVJA186FFYHD9ENRYD2G", + "78KQESJPR5QHPXA2H7P8ZETJY884HJTYK54VX3CGBBAR68BE4ZB0", + "FZZFYJ4SKJG52Y78SPHVQX5K79XEGY52B6GRBP0NNC9BT4EKB53G", + "KSY7PBVQY87SNT458FGJXPADX6CPE147EDHTCTSE673CBACCTKP0" + ], + "h_age_commitment":"75CVY8V1MJBK5P06DCQNGASS94Q8EV19WN47YK8ZDSY8JPBZ2YWG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"31K82SCY20Y7Y8QAHR5H5SHM8YF9XSWGZ7034C3CGDCQZ9CVJFAAAPPDR4S7KRBHT68CZHT79WZBVGKZ28QZ66X85MKN24EVCRBMJ10" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"0ZW0WKB00S76DJHVX1RZ9JATZZY9PBF2EPPW525RGGBP1S79RD16HG71JC70P4TFHC8BA3APED0PEB3C0QA0B6YDH49GPHPRSV37C2R" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":10, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG", + "proof":{ + "num":2, + "edx25519_privs":[ + "M133QHQ1HCKDCS0H3RRFHSNTYYNSJZFFQMANQKKYXJ3PXQNTTDB2NAEQGRWNAYW0RFC72N4CY3PAZJA8GFP0CWZ24CG1DT8FTGS6BPR", + "50DNSEMFZGAV09RJ3Q83HGCBFCEXN7Q4NS0K1FHBZ18KRRC78NWA37RGM6X8NZ76VPVM89JF29S21EDNQD3XYAC1JHW86RVDXYXTZ8R" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "Z89BQZXY5DE21CN4YFKM3BAPEZJS4RJVKF937Q50MSMEDYQN8YZ0", + "10EKZNBZ0D90J6XS1C6X60MW367J6V6H18EH2QDXZX4Q8GXPK2RG", + "SM213HNM4S4E5KMQJSE740CJTV90XJYV4NFTP15YKNJBRE45HYZG", + "MTBB9PFNRE2N2617ZVDMT8S7KHCRHBS478PTZ9T7KWSEH1FY6D0G", + "HMWM5GWKE6DHQQ4ZXJ4E4495C653SHW9SS97M9ZNVAMRDB5BF8KG", + "T023XHH4FPARTMZ7HSWSQ1QXGAG40ACC71F0ZD7CD0FGP5P0VAEG", + "C3CJ8665G711X417BHTH9JJ3ER6N6313XJ1VCD6A9T66WQRQ702G" + ], + "h_age_commitment":"DNS7JK4WZFXM05WGK6BH4TMHC7CJ8GV5VDRHSG7Z1S54P4MZ3CR0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"ETJKY3K5JT89N7TNVDMFE5X6XG9S8B5VXK54ZH5B58N06P3ZTSX5G9FY06M159HGVFPX4YFEVF9E926FDSY5MTXRT4BJAWAWMTPW02R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"QC6HG3X8YBF0K1END3P5DTN2G2TCPERTBN3QQGV8GWB14EJFKB9H86WRNGTGVNPS5KPHFR0Y5W703EGSFFK636NSXCFMG17QGTZPM2G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"FPKW42VE1MQQYFX687C16N70T0WQP7D35CSN5JTY8FDQKAZSG8P8157H0TW69Z9HSBJVX01M9FKDCR32QT8CCDCHZJVAYN457F9EG2G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"7VP1ZY4CFPVWEGZRVSEP4YGBRCW9D2RA65KD9JDC3T50W40YE1BXTW1B9QEEHRWPM8KVZG81S7H1WP2AYXFHMDA56PZSW67QQ0VF000" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG", + "proof":{ + "num":2, + "edx25519_privs":[ + "VK07YPE0NK1ZQEHZNC85Q8WZJDQEMG4MEQGQGF5WBCMRRYSFSM6YWXCSVY9XAPP4SPZZFSAR3KGV40J0G1940AP24KMSGX3KPN9SFK8", + "T6V974T5B9KWXRPXN3V9S1AQ3SKCPQH52ESA11X3DDPHM1R3DC54M03FV61T39WSG2F4S190NTEXM06B9QG2BD1J6TSWMJRV23CSFRR" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "H7PB4BBSSHWMBXAH5DZ73NQDYQ3WA5R0PBPB0W99Q78HWKBFK2P0", + "HPK30HVQK8CXRAF1A6Y5H5W9MTJX9ASF64B2RE790YFH1MVG2D7G", + "JQBYTNH01709P479QZN670TPDN51K3VK49Y33CB8XVWJYNSHDBAG", + "V1CPG3RXJRRC74KWHSDE5GD9QDBEH9C081N30H926RCHQQGBM4V0", + "9WVJYHVTT97V8N3M1A50CCX9DTYK76MWN646JBQ46WW5G1T9SWJ0", + "NGRKFN8P83QYDR4ET0ZBXXTY83C8E8JZDVX6M0HFA9V0Z0X6RH60", + "6HT1YSKEZ5G6HMZ8S9EXX1W2T1AS82JWAECW3D219KR5W1NWNW3G" + ], + "h_age_commitment":"4EDERFRQWQS77WA0H7T2HHD0EMX4Q21YB96V04QZ4F9W06QX1DG0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"MV3G6VWKPB46Q1NZSK469B07TCTXQN74KT6BRNF68VP4GFR601675TXZDXN2CCEJEB0SS138K6GSXV8PDAN007PYYYANMSB3DXX7A28" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"JEE9QJGYKV352HJ6S2VVDZMT7PX5X1ZTFT0KFXA8B7CH57D0K27W29VT3ZFCQJ9CTQVJ34Y159XWHAPQYV1G3HCV96BYB0BXX0KNT2G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"CJ6SQ60G80WK3AAD4N9DK4G3EZ6S2E3PS7WB1V1DK55TRB4PP7SDK6AH070CGSGBH39YTBW2NN01YH9DN6171FCQQJJFMQVC86AQT0G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"H2YGZFFRWPPVQSNMQJ2MWBSXCT9NEPTK9VAMFEQP4R4KN7Y8VDNHEVXZJZF8CJSWJDSVFZTPZ814450W1HCTYXVGQXYK3WESEMQ9R30" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"WJD4GQMZNE0W681CNHQ68WMYEW9XPKRTCYVHASJCYFYZ3Z69T7BH1PW6ZE1ZKHYBFFNW951D206E63R8S6JMTGVRMH7TRFY14XJSVBG", + "proof":{ + "num":2, + "edx25519_privs":[ + "714NRGWG84SJQAR3YA4DQ0FEZR80XGK7ANY7DP4FFZGCCJTCMR55V8XDW3A2XQVSNTPXMD6T945JAMKVZ8XCZE1V77Q3R5WMNF7C0V0", + "YG28RKKXQ424X4J2PXVS88E4MKN8PDYMPBSCHA3HG82618BZE03XBM9671EBQTEP0VP8Z6W6BP4T4FMD19EMT4BF0WBYWRSSKM09T68" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "JNBQSWVTZQBDA5WG2CWWS7CDQ58ABYGAWMJJNH3Z6G4D0VZZDK10", + "W188FFRR1XM82R4KGTX0DR4H78M8HFXYF6E7V1FRA905VTD1DFRG", + "SAJ2KY913REJRPADCAE6MSBZCQ5S42KVGM0J32NE0Q5QYK7Z5Y2G", + "QY2032YNW20C4AVJZHDRCH31KPES6208MH5BYKCXNZZ9K7VEPJY0", + "AJA3V6GYSN05EDWCN4XXWZ5M24B6N0TH8EWX7B2FG9EQEW9ZXDC0", + "NMY7V20NA5E10MFGFS1BXV9VHYBECRVV1KY0FVR80FN67N2X5K10", + "W3D4368RWZJRE2CX0GYYTJH7XN6A2XQF9RKCGM77P1AD7NNMWXT0" + ], + "h_age_commitment":"9PFYHDPPHFS8TWFWKD2MQGSAB0M8H2ZE8WNWYGJG715K78DS59Q0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"WKRZA8WKX64J82SSZ47DNP7SAK2HG86TXD326H5V80PBSX77WQ4F7WPXGDY6QNW0YW5PVY0CWWS1BKWF0ABHPM8GACGWEDXXSGEKM2G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"6NCMP3J7MNNGJ8DVXMVFS028XW72N4QVMC1R28PNH2SZC9SX11NC9E60BFAMXPTHA1E9JNTXEFG9D2P6J4ZF3JH7TAYXC0Z97H7NA0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"ESRBWM5HGRVX7DC4FZZA19WSDN0ZPP5DR66SCCNXXG4F7YP7SFPHTNBR1HDE41VYA6XSAHHQN6YZS8CF1HFTSQAQFB8C3GSYG3M7W2G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"ZBPSD3KSJFZYDG667Y3DP7GDNDS4TFJ6RE7KS4Q5FVD2BK4M87WTC7EB7DBYFNZ5B10XPPNVRFSY15HEEX3QRD5BHGKTXAKN52ACE0R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":12, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR", + "proof":{ + "num":3, + "edx25519_privs":[ + "Q0NSG53HR639KFD94RBB8FV7Q6EGBM1EW0PFG546Y3NNAPD2VHF17ZSMG97HWGT13SFGSWHWGECXXW55AJD5FMVNBMNGE0W3W922V5R", + "P3VWNJJG79TJ9CMVYG7V5YMJ9Y8566N9ZAC7BXQ1N0EE1TREK9YPESHJKFFE3G998BF8TNWDDAM6NQ4PCJP8NGHV5TE621A17Y4GA7G", + "13V5XA1RCEJ4TA8RBHWFJYF787S1TG0QWN23WHEXZSB5RK62W5VK9W4K5TH8YNG7C3AHSC05YXXHQSPSK3PBXP0C24NHMSRS1DKN1K0" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "3H8EG806F0A3G8Z40WJE8KCT47BMY2FJXGYR08ZDDQKSMTC98W6G", + "8PHVPTWH5RFVEG5Z79R5GR23Z1MYHYT5PWCNDRXMN6GHGPFVYR00", + "878PZMX56KYH72659QG6ANT6A36YF8Z9H833K230KWQBNY5XMRSG", + "1CXQ3PPFG7J1RXVZDQKVF32G9TYSX71Z1SXT069MNEXDSAYRJP5G", + "KTMSPDT4VV4VW2RR6F3TZRJ1QD6T3GCJDQBN7NQM0R08S52VMV40", + "5NW2DQJ6YXAGRB3KSB5J9FEHACG7N5V075Z3MXAKFJ0B9AMRQMA0", + "6NZR6NVNWYC5B40F96AGRG233K1W4P065JMKT090N36X328XDGTG" + ], + "h_age_commitment":"7APR4KBWDQ42VSB1FGNCVS8V9QET9M9FKT420836JKJR8ZXDN7QG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"574BSMEQ9430MESJ16T32CF07QAM8X5RR7ZYGR02W308MGNNQJM957XJRWHFAE779WHYST3378GSMQ76F1RPJQ200QYS3BAW6WD4T3G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"8YA38DZ5SNP5TM67XK4VY5JXQ16JEWVZ1BY3R714FJHVR6JB34RVTWJ60VA1Z6TMBR4N803A9YMK547ZRZF2N4RPGP8G76H7DKZHC0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"XFDQ936KWQP50BB2HVTBNSMDVF9ZWTFCN38RCW5FTF7030ZHYXAJEDH1PHC3N21Q04XQ8JCDABQ8QAXCVEHA5T7VATK917Q05P3P838" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"DZS3HD05D5YBYNZXSYSAXJY2T65WC7NBC03CVCPN0NP1M52BY519W711HJN6VHFY46SMCR038CJNWBF7ZV80ABTSPKQDMPTJGKKV02R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"M2HC2SA5SG645J62188X8DPV3EKDFZ1B9XNBSF8C094P27ZFS40NS9KT3EN9KXQAWA495ATW6424ER71YHRHGNZ7BXZJT85GZN3AC10" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"4PF7ZGRARAY5XZK3PVKGR090FCPDH10X9PKMG31YDAYEMNNE04XW9808ERNK2XPN57RWWXRTT07K9QW5C5ZZ0A0B0RYGXNP4NKE663G" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR", + "proof":{ + "num":3, + "edx25519_privs":[ + "42EC4C6Z5F18CS9AR79SJG09R74GARDCWD3EKX55VSYC57FZVM7ABSMBCM38PA76Q4BDT5ESQVPQPEJ4A7THPPQWENZEZBWX9JRY6M0", + "63QM133P2X2NA5FKG58110P1NPR43CA57C46JGRKDAXQ1TVARG7WGK1RJSJV3R7JV5TSS71YEHPAYDYJT7HJNK8RM3A3FQ4EX79ZNK8", + "BGNSFS5CQ0VT2CM5Z72G7BDJVFVYKYHF2G7T4FM1QZ041Q90PG1Q3BNC64MPYS3EYTXPVAXF4MAE39SHRS8ZGZWPMAEP5YFWZJTMZWG" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "25WZ3MHW2BFSQXM5KB4VVCFYTTJ7GSFVATVS035HKJKV5NTRNBH0", + "JSFFPMKAECRT3B05PFV7KKS2HZ8C8B7SXMT4JT9X9D7DJ3K9ARJ0", + "H8XCP1HARRA448V4NGK3A1TK7D21NRZM967BF78SSN4XVH7HJDW0", + "VEDW1ZDYBM71KD7MBVJ6NHVS1NV0TTQ6A5EA4NTME01Z47924R30", + "4S78Q5XN72QGB9BQNQZM235HV9C9TBS933H5FNV5WVXXX46RB7GG", + "SJWGS4ED0N6WZ1EHVADWE3DWF7FX1ZX0SXGE8RVM711WKTWMFKCG", + "M0R3FXPAM0Y9PKN8X555X9B586CVR14P8SVDSC4R6BMJ6QHY3CN0" + ], + "h_age_commitment":"75277R1RTEK2GCDQD1TNCM0C27R44CNXJF2G0M92FV6H55YZJW40" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"A7YCDFA1RH7QPRX0C8NCW6D1N695ASKC5D2Z1P6BET1RN9QXFWFJ0E8Q73TV192DTD7QR2FRH7P6Z0GNTE5AANA7DC8MYHM9S6MV20R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"TNMSWDR0W0PZCSK31V89JRHM3J08FH6JV8XZNF3A3D8QQW3A08XY6WMX13WWRGSSY13Q5EVCY8BEAKHYM71JS9XS9ST3MRFD7VDY428" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"1B7X9PN954NEKBNY1MDSKAE4E2Q873G9FH51EX4DT7TY2M632N9FPGB3VNBYYBEYN99YB17RJVAZ8DNP2THTP1MB4BCFB0DYMRQS428" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"DR4FT76BSW80EGPZKKZW0MHCYZMG9W82VXYEG5CQQDJSP090FM6GH79JQDY85R02V1C3W7C6WJSMHJAS61QWN6BANCN6VETF24QT40R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"3N2W7Q64RJ0974GK7V6EM9BP3HFKVQTJQAG7QQW60J2BNW15VDD21104FSGM5HDKC3434TS632BFDM9KV7WP6JWBFFXRG23SE0FVR3R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"J16S8RQT0J655ECDK60JYAW823DY7ZSDK7H8SWMACEPPJJC1R5QRMWCSH546Q42HTJWVN9NMC7BZAVKRAQW2XYJM4NK6QRSHM51CT3G" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"4D6T861B48BMYT1PS9AWAJNRJD7VCN7SKZEHDAXRMNXF3RM5WJT430WNRFBGMJX1QVBQVDWKZMNQ5FR49H5RCWPS009JW5P2TH2N0DR", + "proof":{ + "num":3, + "edx25519_privs":[ + "D9E9JHCFGZDYFHPRZYXGPZXS0Q5AKTB8EXM2T6MWS3GE5K6DVW0YWGMB8GTAT78NSKRSGGHEPC021AQ7MJ2DZ9BQT48M1PRY987DYG0", + "7EVVR1ND81YZ9CSQ1K1A1ASZFD93YEZS0YDQGSKK9D2174ZTT44VJDAQGZQGGP55Z8NYC43VR6G0ZW3H0AN589N8VEBC5XTG9K3VM08", + "12VGPC42FDRG1FM08RV63NGXZPA8NPDP2WT9JD9NMJN9AV7Y4G60QK4GZF8VB1N0AX2PRWWV6Y12SY0ZQEYR8XES6JXKETG1P538TC8" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "77H8K68NFH9HHTQQAV9PB170F48HGAY2T3WXMTEECZW6VJFYGDA0", + "1TGDN6K8SQYK9E1X7V9DZVK0P0NNX4S3QCC0RF78WQAD8QYC574G", + "Q9RXQVQ7FK9NG9HYMD7G3AJAKY2AD65VMFAEXDV2ZVM39CM3WEEG", + "70NPXN7A7DD1SYSTXTE3R8M3VFE7GE3CJ87V5JCJPFKVGGVZ3M6G", + "C5MZ7K9VTC95NWJASZCTQ7JG671A9HBMC701A23TWWRQ7S5TQ05G", + "6YGYNA5YE3SZ23HPEX10AMJEGYP9902S0Q2D36H13FRDMJDCEDP0", + "HRA652WKRQ0RKEPWKGW9RG08T8BBMX2M0TZD5DJVSTZ9BE039NRG" + ], + "h_age_commitment":"TQMFGTNAJJ4V3JHA3CFFWPCGFB7H7FTGF17PEYW12R7H9ZG7G57G" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"TSSPJWSPHYBBM9M0G15K4VR4FBM49KARJ0S44YKPP83PD5PEXND6XJPRNM8PY36R2ZCXBBH55YNV7YGW2CMY630QQY1AM7NAEF4A230" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"ZTGP4NYXP3MN035THX879M0NTSP7TTSTFKKDQ6C946ZY5VYTKAFSQ55YD558NZA5YXDBVG2MYM89SS8T6FYDYTTX9T60Q86AS30AY0R" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"VXHJRSHB9EPHRWEW1RNDCQPGNESE87ZPAK0V1TTNPS27TVDZHA4FHXRTEM7GBHCC57VXSZ4VQE66QQW4Y5NXF2YBQAABMCM9ENZXM0G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"XFKZZEQ31T0HK1DH2VCHB19Q6ZDVWE3GD3DGG14TVC2BTXGTKDA833TCDM2RP3ABNPJE7ZG7M7YDK1B95E0WPYZE2B4VC7D0QRBRT28" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"EY5S6YE5CAJ0SYMQM1C7BRVDDAZFA1GQ9Z2QB2YBHDMHARVDD3B0P5TDR2YMX60SAQ1954715W3GZ05TGN93QDBNFA8NTSD8AQ4KA3R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"BZ45DRD5QMSTPR7FGQ77QYZQ49A433TG4R9XVEWBD5ZVTGZCBWDWJPVXYWWM2YA253QTD2289XGK59DM8P9X7GTP48QQC8HJJZY8E00" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":14, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30", + "proof":{ + "num":4, + "edx25519_privs":[ + "C2J1QM0X0DEJYGRBSRYSJDAF3A8HWKBABF7WCSZH3K1CZP74XD2W0G85GDY1NMM2AQKCXTSDRDC4TPM90QBYGCZTR2FP3ENY11JH6GG", + "V2YF3RFD50768B9D2Y80K5671DB272A4GMC1HFY1M2VAMNNQYDJFRJQ94HHKS5Y7BFWNK680DJM8JE9DP1MD55QHA88R81H3VXXNJ3R", + "C384Z260TM7TDSR6JVBBSP61XECZPJHBT25ZWFJV4NWMF9FVX12Z87Y396JPX3K0R7T91S9BCDPZVEKG3G40E6H986Y7MN090S7REKG", + "P1D6ZKQPRFJ4NDKSKP505NAG1HV0VGY5DVNQVVPYETY2EZPNBNRFHAKYV5KPQAK812055T16XF25QGQAZKFG9XSMSAVB50Y325SC6VG" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "JXYSGPQ1VRCRW2KYQNZ9BMDKSZH5XZZD3K41NKH6Q9FAR5M11XN0", + "R1HBPSD7SA7H4YFJE7KGPKF1H0EYGACEDFEZ3WJHYRJYJFMVWN9G", + "YA1050S5A7PJD7ZEA4EA7J3THQG436DNN5AJVQGVF2XY1GQ9S6N0", + "FN74QAH2NGT2T5SMFRE8FJ9MS9P3CBRSF8QRSZZ4GE5KMMX4BDJG", + "77ZQQKEAXB5H1J9DSPFPFDBC5G382ZE8AGJ6M97ABTNBRHRHXR5G", + "6M222W93EDZXB21WMVB6AB82ZJX8F9JXCZ3NJ7690X3H5JVCJWKG", + "YDZ0WKX5D37J3DF4PNQYT8P0ZEJ13Y89S1KB1G8FVR78MJS0DHS0" + ], + "h_age_commitment":"F57X524K6HAC3JPWMR61VAEZHCH90DHSMTBCKYYZYK34KVRB08MG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"SB5C6JG3YXEX4ZK71XXCR5KXQ8K33N8Z5H1A3X57CSXEPGF2YDB66Q82JFD49ZJ8QZZCRCZZQAA59NHB3ADV2BN7VZ4FXG3NGKSX43R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"VT5ANRX9PGDJ2875M16ZR3AQJ07NS7Y9SM7T6P1DF059VEWDVQS5XSCQKVGZ2DMJJX41N9RQZH9ZAZCHP3GD8A2DZM9A1TBTPKR9W20" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"NJ7ZG7GR3DQA96EE54D56CDXG2CJ5NCCPK4ENHFGTAEBFE8QD2811VSG7W9AAJW6C4H6MASSZ7S56BPKSXSPS0R74805EHVZM473R0G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"62KYPKJ7S6RN3WBQHH7QBX1NM6MD2431SQYFWNDSCF0GNVSZV6DTN205RYV13CPFSB2WB6PV7H5TPC2TZZMFWG8MBR3WNRTT4KXW22R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"PXM0ZDPBCAYFX00BNJ32JWV93SEH6ZRYH6X9YEZYD2X2JZW16WXYBYBRASJV66W6K98KEXVBQKCQGBF61V6NF6FNSSJVYBFM88XNY20" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"40ZF57RQ1ZXBEXRN7N929BT4QXR51VTHP5RVKG38V27Y5H5JH4MJQ6QR7YMEY1PCSA45XE4E3N22EDJSBB2YZCNBXJWZQDMZPC6TC1R" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"MQ4CMCZZ6EX5XDR0VQ7CDZED2BV7DDED49PD943TVN451DE527YD3TMA0CMGYHR6Z9HFFMAFV5NCH2VSS7AAR8GX8Y0HQ313DCP402R" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"98ZMBA4B3FGV2WHZFGZXPZ5TD43MXM6ZR2D43WFTCRENVZ9K9G8CE2FFF8QWDDMVNGBNPT9CX148KGQBKB7QNND0ADPPX8Z6ES5EM3R" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30", + "proof":{ + "num":4, + "edx25519_privs":[ + "AV8Y74SDW6BM4GA27DGMD8F5H1NWGFVETKY191SDNE08PEBS7030RQWWAHA2J6NFXJZT7N0F5VQVVFM55207G0VH5N2DXW5WRM3R1YR", + "T6MX6CMWBHGHX43NVEB2DKGKDBQHTZVFMZPF1FDTX0RMK7ZKCG2JREBVM0PNYPKA2JH4PADW1AM96024HK56FM67CA21FHC4NA5JQ7R", + "YSC634TSZ6DX0YMB41BYGBCF1Q0R924GPDXDK5RMN2R2VPZXD40M9HGCPWZV3RBV00HWZMT0C85M01A9JYN8NH5ASJSYDF1VHJBT5C0", + "9KZZNDJ3YVEBDZXN78R50768EH9N80GJ1RVABG3S153JWPMS8R6FZMSE01W211NH3SRXXB5K0VG49W5K0AMXKN0N5KYTKR2KMXEF43R" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "29DXPCPX8WB9A0FWXNSQFXN0NA0B8A5AQKJMCBKVF5VWVP8WHSZG", + "G46MXPEPFHHK612QZV3EG6MWC1TY9CX4EF1GQEXP1Z9GRZ0TA1G0", + "WPE8KDT80RPP777PXG4M91ZGY01ET5Q4ETBN5XGSE68PS0SKWP60", + "RNXRG8QNNXS8QTCS0Z3KJ8YNGMQ0V1ZV9DH4CBYBF2EG4114QEXG", + "FT5BDFG1NYTVHJDAWZYGYX5XAD1MJS0M4P6RVFQ45NJJ8QTXW7K0", + "RQA92P1GVP86ZR2485N5HY8B6ZW4HHKQ6TMVSGE37KY554KQ5J80", + "E91D0NR5ZZNB50M9HJRPRKBMJBNZ12Y8KG1CJM2YEECVJM6V3950" + ], + "h_age_commitment":"J0F4KNJ3RDXZEKCZTHVTCJ28YDPMRC7MNGWT5GAAFN0HTJJRTWEG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"Y3V0QGX72M363B4SGY8XVEY4JW2B8ZCRECZDRSHP6977BRFCZ8QF0YAZY8TQ8D1P69A3P56CK7PVTGGRESWYHMQN7GJYSXGMCZK1Y2G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"MAEQ18Z2BS6893PKSDESZHPWV57QYY6KX2CZYRNX86CQYMPW55CTX3CMHGV70N1AAYTZ3PTZJHEZTD92Z1RXMEGFR9N9F8HE0SF7W30" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"4FYXZDWEJ8D7KCX7T3PKXWP6FWYS9DJT9P01MW6HH7897X0ER0SGVST90GTHAQGKP9897WH74HXCMFPXG4A1K090FE2MNKRMZRQM230" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"Z7MM1PWSF0XAMWYSNDGW4AQX9HDNNAK2MJZDW6P8XR6JYHQC51RXXV05QE75YG4C4QY069RCCZYKKZZG0X92NDB0PY3TXT7SG7E9010" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"HTH457HJNNX7P51CN3NCK9F96N3XEJ7B77NP0Q10KF15DQ24VZXF3H2RQ8P4XS5C06KF2X3XMHBPFJFST9ESD2220J3GWY9CAH0V628" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"24DV2XPWZ5Z0X3ZR61FRRRC60PTS0YF4HH7TV29VPBFT2ZNN3EAM4MWSMM1PZ3M5902W9BWMRE3P6FGRECRPZBVKHNVF4D7WCQVP808" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"B88KTRKA6Q8ZZSDCP1SENPXVAPE4WQ5W2C27C47G6D5CVM67F33DSR58DBHPVRYK34ZDSRSZ4X3SBSTPR6W0K763DAV3TQV8AK09630" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"588FXCK60XCHP7R2A8MQANZKWASY07Y0J2N9T9A1BHEJ3WKNKVF79ZAPMMQDWB5F35FSH27TC2AZP838CB67KYZXMQ1F8Q13VXXMR38" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"4TFDGH0K35WWXN7QGGZ7BS9EWNS5AMC9TM5BGWQBER9DDWYFQPEHWM2SXWP2NXGGV1HWNY6Y5RVEBFFB4GAVB2BS6HQY40DBKJGXC30", + "proof":{ + "num":4, + "edx25519_privs":[ + "S76GDBEWTJBJNYTE5NYWHQG58ZDC2VSQDGHDMHWFYJRHE462HW7AJFE3SK6YC03PW1AVYZ918SS1KXJ9FD13ZCGMFBRK9ZAEH8QGV6R", + "JRCFDWGEPXD6X60961956RT7CDG1RJGP8YQ4NK20V4ARCT8KZ80F9BGYC5BVPXKPEKQDJ90JY18RP54K2CH6950J5Y0TK720WDT2ZQR", + "4ZM75K359BKSYYE4WKMXMHFFJVVG97ES2JYH69T4G8M4YTT2BM711DKGZ18G6Q4ZJE9D9D31XJS9QJYN8015HSFNTGMWAGSC9HFMR2G", + "VD98SRXAZBX0RY9V3ECB65583YG6MBXS34DJ3NRYXC73A5F5C030VMSV1JAS3XZN7XF326AQMC5578BBZHHZG61ZFAABV2DMP2Q2V6R" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "VRBJDFPWP58QC9P83C630CJEY20ED4W4A4F6FKGBN4H2CCNMNKX0", + "RK23WEE30QM6JB1XXNBCW3FN8QCCFX1ZAV6K5C29HDFW67A5AB2G", + "F6CTRCHQHB1Z7QN752W6BYB46PJH6P5YE6RHWVQJJHDFJBT5KWNG", + "G9YAYSVKMEQR9HX40WC95X2R6PQK0DBSHN0906EE25RCDCSCT7K0", + "Y91NF7E9ZV9Y12D2JCWZT830D8EAP4WVNENHRS7MPBPSJK34G46G", + "W64S5HAKYGQPEFAAJQ8KVTY8NPYXTY4QYRK451AJZ4WARCN2F6F0", + "6XG2K6CMC5B8EANMY20PPFZH5391VGQDJVHV3QC9VM4YJXGYRRV0" + ], + "h_age_commitment":"3SA942ZZRXBX5WMK9HJ74DZPEAGG14SGQT100GV2DSNBD96M1NN0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"WNAGSKC0Z265G5P1ZW996RS5APG26R357D9S7NQ3GB9XRRPGP0YNQAEXWGXKYH10HBW4KJZTGKB7EMMXKWS1ADZWGNJPF6GZKX8BM3R" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"JM4Q9NB7058A3CV4VR2FNAQN40BJ2ABZ8T1N8WQZWJV2G7206RRV9WKR2ZNXDNPPJESVKFRJ1ADBX9KTQ6RZ6M1F0EMQZAD672ZJ828" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"TK8R0WD88D66SE4P87NV79SJRYPPK72ZAS7XZGZ8B1NJSB095GBT638T4DW087472FAX9N05MJXJ7YCQH11JVTSS508GGZA6WHTC81G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"920C6DKCME8Z5WBA23AF6DMEJE43H2VET8P0B9GG472YQ9C05KQDAJ06ASKQMP0A3TBQ1RGM4T95R02ZY5NM6XJ1X4MM99DDVBW723R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"F2ZM081TPWM5V2QAJ03QAP9GSN4MBMHJ1AM6FWHDSWTZ9RPYKF9S11TF817CX0PXVWGWGY7SRB6VJX0CENBM8GETS7RWVTZAAD2XY08" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"966TQXC6VKE3AR6FS9MC6F87EX8CDW8XDKFCVZ91MACD3GMZ2EGNN26GS65611BVVE9C4VBHB49D4DJD2TMP6P0D9RRA4TCGD8QFY08" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"KZG6WW5C9PJW4XBFJN634CTA60F5GKYDBV5GK7Q0RZTXCE2TV3REDSY9V7SW059G33RGPEVYR3FC17KJFDQRXM3Z41WYXXQ0NV9VC00" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"57E5HRTW65MRCCNXZHGA6VKRE953NJ7P1AWPF38003R81X2CCZKZREZKVJZZM7530AT6163XDTM6NQ5XMMVAFZKAQB3C5ZW2D5P7C28" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":16, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR", + "proof":{ + "num":5, + "edx25519_privs":[ + "B27SDGF9XSZAA0Y625XB5G51J2XWM6F7HZXJ2QSX7D2JS7F2C9HHH0ZQCQ4697HZAZE2E28P7J2B8VHRS236SCSWPJSC2MGXEZP2HXG", + "C0T2J754RHC9ZVW5Q6NDPC221KMMA35G0PE3KJ783VTY8FA3YXWHSG2Z8WG36ZN8WBCFNY0RFS8ZDHFMK3WE83S2KA3FZSFANDMX3CG", + "F1S0NA77BQX493QYQE73FA563YJDXTJW9YBZGQAGADQB209CPHMNT335WN1W9VEBN9GZKNCG6AND4C370QM6AHEWFP6WX8HJXWH478R", + "22RB2HCYT9TG15NKNGQ54ZTVTMHDJ7GXHV6EEQQNHQ99YYKNM9A230KYNB7KETV3FQSEJYWGRAJSP3EC5GRYYGEGDRPXYB3RE36EJQ8", + "B2ZNM5BZP092MXWG7K5EDY4A8WF1TWT8W9KRPPMZXMJNA004ZXZ04Q35GKM69J33SD7B0VE6BFHC105M0R46ACK5WQKXAFHVJSE4KZ8" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "QMYDVFG4XXDCN73CJX3BEQCMABXCMX5YRR34EKNKHDHNX8FCKHQ0", + "7E2CCRGFBN869VH77MC9GVB5WB61RAY6A865W0EH3RJB3C30ES7G", + "K53XP28HN14CWJ02J2DFC59XCSDF3C4M07FH63SFJFNV4F7B4FT0", + "6EKEP7M7HYAGTE6NA4VPM8M5AWQVV711W0YRJVEAVBVAMW6ARMG0", + "XAN6SFQQDTX3JMGC59RH5567G52WB5DV64E7N921NX4KFTXVXG4G", + "8KM689TP1V3H5TKDAE58FBE447Z365H2NT932K0GY2MNSS88B2ZG", + "KRBGEVSS40VTDE4NBQR561PMY2STJP5YSTS9NM5NHYKP8MFYZ8B0" + ], + "h_age_commitment":"JAZVVQZ99F9FXJMC9CS34YAND421TP7FB9SK0JW2S3PWEEJV8T80" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"VYPED5Q80EQJN5H1G83AFHHWY3ZMXHG6Q8BDTP0GNDZ3GFW55F1W1KD84N7S9WG0WBWNZBRMYCZ7X1QCJR6NG11WFSVPTGDMQ0F782G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"DNM2S96K8YB69NQ0EJKBNQMWCT62KHCB0P7GS18SMJSWFJ326YJR04C5FFEB7JW5MZ22FKTJ1A7QAK133EJ99YFH54SX2R83M7X1A3G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"K1AZV4B6RVDDJMHRFYND5MQSG5JM47M9271D4K1K2C4FQ52XS0Q708G95N5875AYF1XKS4KBXT3MWNYJB25C13PC0YN48NRP9VYM43R" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"3PW45FRR0Z0WKFG0K40GV5P5THMACQC02GK61JKRSM8E4A0DQS65YB9FYQPC723RN6Y82JZ7D1WTDRS9TEXW7R0SWZZPMNBJT6Z8E08" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"T2S561D1GWGN21CWYFD78EWHSC2NKF66YCFEMH03MQAWA5J392CNZANVT2F5AWVCNB5VQTJ89DKFBKCNWT01PYJ4530PS9TJQF87R08" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"8DHJF33TN018TTP72GM0H8RZEGAD0X106EJMG1Z4TNEKDZHGKN0MZWZFRKB0R48PJGS0HNJHN6P3JPPTW3RWVXEJD6MHWTYT9EAT810" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"KRNPMT235KATC7295EM5JP2G47GFY7H3J6JMFRPJA14FPYVNCKS3TXA0FWAF9M3FFT4P632KS07FEZC7MGRTQYJE0KH23W0T7RPVC20" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"0JHJV3SG0YXCKMDEZPAGHNBQ6EWN2JN6KZJT9DHHXHA3KRCJWRJAP3DK03374NJXXYD5JAPJ6MG1F940TEWDRDDWJ4VAC1MKKXEDE18" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"B98GRA39R5DVDJ0CQ72FCK004M4B9B8N7N5KN79XA8WEASP0S4538401PY6N6AXRM1FPDMTGQXD03GPYZG362XHQV5G4K7594HHNM2R" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"K2YXWTTQEYF0CKX3SPFXVSE2ZANZMAVAVPH37VDQ1SSDD6PX49ZJJDBB92CQ0TQW7QRTP8ZR9WAK31YFYT838TFEB3V35W74K0NQ21R" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR", + "proof":{ + "num":5, + "edx25519_privs":[ + "15KHCP2N9V5AKTR4C77J04TRHRC97AEX6XATACEBPAQG3S5QAW4DRQHP5VA7F164VN8M01GCAT923CEBD4RKM4AFRA6BA1QRYYVK8H0", + "91G7GHZFEJKRJB6T2E47YM5VTD0T9V14EW8E0AQVQ1KTKW3BA014620ZZPMDTTZP9Y5X89K8DZXQY193D2ZCNQEM7YWBECNJ9HC45S8", + "D6DP6WEC8GNB8CYSEA87ASASX693XARG9VARN49FBA4XMEXFVC08RE1AKEXD2PYNMME6EB0NRRQ6SX95DA3P02DS3KWBER7DJCYMTF8", + "YXNZBMB87J2VXNT3VNVS3V0FCB3CEK7PC98VZC5T0BN3EZ0H8C3JCD9TS68HMFWMWQRXJXVED5TDT6KGT2988ABG6VVGFZ56HQ9P200", + "3SX0955GGDVX2B0FCG1CNHD89QYM5ZDAXK3F6F1E7Y6D98MFBW72Q9CK6205T0QZ3YZCQRMRS6YFCZP7HH4G7115X57EHZ763EK4ZSR" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "932CP2020AFR4VKATJ86JA923HFG5DE4E6CCPK603QVW1H2PGE00", + "X7NCGB5ZAYTKDE3KY19B2275SGMCE3KQRMAF2MT6FVASK18FD0A0", + "B0MH0ZVRHV7N32J0C7M23WE5G489YJR02MSCWZ38YP9GA7TVVHE0", + "WHAWDY6BPR0529086QC82Y05K87G2GYMK7HCPY22QGWP4WQK2KHG", + "T9C1XRMDHJ7XRHRS8K678G4SAJVAHP2BCS7Q7PWKSCQQGWR1DJVG", + "1RQBAY97EV1KB8CZA84069PK66HEHWS0P78J7Q0277NN5V9SGSRG", + "MPM0YA6MZP8Q9PRSJ7EDY6DAH5W45C9RDZVNABPC1E10MPQNWVW0" + ], + "h_age_commitment":"Z7BFSWE87ZF79ENZ3CM3VAS3TV8ZQRQQ8E0ZE9CK61KJDFTH99XG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"8H31D91M5XB2NA8EDCVBMJPX37X5JKN122CQHVADKDKHQZVR55W50WBTCBVCG32GEGY6PH7HVDKBJFMQH7PSRWBMV0Z6MV2H5YNKP20" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"7378872NGV64QCP5PRYCPK2PW916FTD81AQFTSRNCYRR8B5JMN5RNAJ2JWEANEW5GNA7PNJZTQW28GMTH282ZR4SZ60V4B3ZRSTA63G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"4Y53Z0E8S3W8JMZZT7J3HB2ZV2XQCX0K7KWR2842WA90SGF7DJED9HB04XG1VZCPT5QZNSR7MVJEBZZ1SP6M5X618H2VACB2E2RZ008" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"6NTBQH98B76K3ZKG20P0T3HMTE66GSHS86J1PJCG71FK93TGB011Z33CE01Q3R8R92MQAN44P6VRPX9NP1KS83KBB3788S7JSYVGW28" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"K1ZVJQAT0A7304XZABD55VKSSWZWW4RYMRHTACQY1QCDKZJSW1Y3TRN8PJEGQ5TWTBR8S2C7EBJW92MMJ9ZXCGX7CQBFN2REQDAFJ3R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"CHRP938VTWPWJ7E00QJR9JJ0JJTXHD1HRH3K1MC3ETNCKJ7816C0V37XAPTKM0KK5N5473ACRFH37YKWWD3MNGCH7FVG8FV32DS8G20" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"P1RK6GG7AMQQ9RKGDPNMA7XVS81E4AQF4W0T92XD89317BXZX6A09BZ1XP8M9S82VC7KM2R4XBAPC6WMNQY2M92BCD1YCPS0EF6NE00" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"EAG5X0GS9H4NB43P9TYT8NFA220MB4BHMMERFTS32B9FPYK8HPXX04RV1B3KGVKA689TVP0XJ51KA1KKMHDPZ11SVYJT9ZZ2S61KR08" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"AQE5XR5RS4T1QAAZ44BHGS9QNHQEG5VS1TC85X12JBBVFRAG204WFHYA1026DF3WQVDQHPZ8ESRWSHVJCHK675YYXD6SW15S802HG20" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"KE9NV1ZV5X19W7MZM2GNHQVKP90TWF343GCXWKZ66FAYQQB9J5A0CYZ6VTXD29BTVFFG78JQMXEKQ0A3Y6F5DNBY8DQ3C6RZDAX3T00" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"3N97FP1E3ANRTQFKWNZJR4VR5VKNQ8GTRZVXW3JXKWBE4XCM9JYJDAACSMFQGHT8S9ERQ4YDE9GAD97FZKWP9F2K40GC301X5W4K2YR", + "proof":{ + "num":5, + "edx25519_privs":[ + "KPDWJ8MYNM6M2BJHPJQS8ZP156JRXFN40S6DB0HKHDYSX4A4KW5R687S8NRRA73YAE3TW4QNTVXGDXVVCVP7PDRE55DBW0HZN2YEJ10", + "TXM3RV8NKP6B89HBQ9DBMDXQPDJ5T3ZY3D5QPADV1DJ86GV8RR7T07VVFDB4E2MY53G0X1ZFFGFW1V80XY4736HNZS3G8W4XRZS5CFG", + "FRNFABAHY2C9CRN2DCPPH19MY3TAZ1ZFEJMYFY7M7XFP09Y5N41CCF4KJCC9BXNQDBKETAHCX05BX6VZ79PWE4W1X5VB0RVHWSCQPS0", + "RQ086WDBGXYX7CTAPKC039M79RHC3DM7NK5AVAH1N2KDV6Q86R062QHNVFH9WT9A8GX2SZPK685JVCTBVQHVG7SA4738W9H7J08PRKG", + "1C19VHCBPXHDJS1FHCZNASJ1QGYPZR6PRB56S6DXHJKZF4X5DW27FZMEEV254Z5B7FKPFPYNQ3007ZCKCCFSKAM384JK24MJCZH6QHR" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "VZJ2A3C3YN83F6AAHY3VQNXYKZCVX4QV0M37MF6QDK77QW1Y701G", + "XQCYWGZVMJFYFYF5WVQXDQ68EYNKEDR8DE80BZKVW66QQPFM7Q10", + "EKG7ZDBSRACCTT8JB3CCVEY2SHNF8GFQS3GRY224HBRHVY65R2NG", + "2ZMSN25ME01K9CAW0K2NFMJ4C0N6A83P0GP3R849WSCQ7A0HSGZ0", + "CEHG8XRAJBKV2AAY224WV3GRAQ1BHV3VZVR5APQ8PMN90VHM9M10", + "9T37PVDPYRMPJQF84G4D96RCCH54F05NAHJV31QK11FKWEC4XKGG", + "QD06G36ZRCT58BENXTJ1SEZ6SF6GMKYX4SXAFYPRPZVBNHPKDYZG" + ], + "h_age_commitment":"E0AW2H3FSA2H62WRHQBF3FYXWSQEM2SA3137VY6TY81ME556FFXG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"Y061X2FQXND7XY81MYZJSD90XTGNRP80DS5DTNB8P01GWDJTV02HSNPTS35SV49NJBSN3GBFF5F9HNJWYE9T5S8PNRFST7YNF7CQ630" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"DSB08W7SEW1JX4F3SP4G0WB7FN27F3V70AKBY0H8BJJSMW0EYKARGJP9J2G8BYFWPSNGHDX4VZ4AK69VHRMKQWQK7V85P08NV1MSM38" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"XCZ101WDMVH8ZCQ6ANAHSFCN39WKAGZN1NZM769E3H8FP6FW7G60KEY37EJWR188YN3YA5NWRZKHV61QB42PRDSBZJCFM33ZXZGPM2R" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"EG6PV3N6XWT672BEYQDRDVD4CR0VNR045KEF9FS7FSWJB7C1JT7T1R1ZB1EX0BVXDPCX2WJFTX1WGCJTS6ER6ZCK0AHA9VXC0BVPJ00" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"VD3Z52FB9FB22TZ2DPW1G3G0NCD5N52AE0NTEZW1PPES92AWPZB77Y4FW8BFV06NXM9GPFB1AAP9VJV18Q7VARNT4C470J0JBT7BJ3G" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"TT8304EQTDSS7CK1QB783DY411HYNVA4JMVCQMKBFEZS1W9C6VPQN55H7JBWSBKEYQGDB0RX0A9C5YTNBGNN974GE4C7MFF7BW1SM1G" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"KQX6CX6WQQZCGQ0YG3FH7WKN14H10HES8PAAKXH7MFSVEBDS5PVWJ4H8X44N5Q0ERYYE8KD22FSVPNBF9PH2GFT8ZSCX4M333PSSJ2R" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"SBKW6CVHT4Q4QPKZPB0T11JQXK86SKR69FC10QNP7TAPTQNYJTH4CY5WZR5Q7F0JR7N5Q2B8R0MZ7QPB2PVHKWD1N94M9EC2VYTFE28" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"KANBG4CV97XD36K601JXPSZKVK7DTDC6FYK9TX00EY8TTZGMDZ51Q1G2YZPED9SRDEJJHDWTN33CGG1KKTFT1ZRA7HBCT62YXXQ302G" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"XXB6NV248YY5QM0F90G0P4D3B95JSC53GD6KJ4227BRTP99KECX20FKB5EJSV6KQMDV5TVJGTMSK6B9SK245S8B6T7J47A4JXYBKG1G" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"not applicable: commited age too small" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":18, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0", + "proof":{ + "num":6, + "edx25519_privs":[ + "80XN7B94MVDVEATJKB1PNZ9YBX0PGP3R724TG78KQEPRVC2DJHYTAQQSZFM4RMXJF6V23VNFSP4ASHADMPEKDMDTA8QXW89KTZDWA78", + "Q03V262DS5Q8C40QW0Y00PGHJA3F20J1T4Y53RVYZNBQMA5AR1DHGNRKEBK007APKCNWCAB5EN677ZCSPG05A70821KKKABH2J86BPR", + "02PH2QB82Z7MCG1MXMTGNAB506P519HT6TQA43N06Z054JXKHDTEKT4SR3BNM9FPBQ6F67N96TZQ0Z77FEK54P2RPPVJCDBT3P09D38", + "83EP71GNRPDATD2RZ12T4B0Q1EM7NW5JEYAG3DBR6MYDVKK05XD7DKV98KQR01RZ5HD319036SD0QPN9BZHP7AXHAXGBKB31VPGCAJR", + "C0N63NT7ZYJXV74XHAY8AE13ZJR8K8PPPT7QHHQ1A7XEPSCMFHEABSZMA67AS4C3JFMGHDBSFKN39GCVHSPT9MJHSKBNKT8Z5TXBA68", + "W03XPWAV8S1ZWJYJNK9TBH0H6AERDDENY1YJG6GD147J1NNTHS1N5YRE9KF6YFVTD1P4S0ZZ71VM7YW7RAV9RDQQ38DPZD5QBRG8140" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "MC06CQKCKJ4Z6T0B8P2MSBSHD0CB4D1GFTJFFDH5GH39VF1PET0G", + "TPTK5YDW8FRQZJ47AP5D6QG21N7AMPDBGN7580APVK0G2D34TP00", + "6VP4VKG15BH779MEZBEQ1XDQCEY94J3B5ZY4N4J1TRBJ7DF749K0", + "RB6MSM2V7MZWK6JGNGZGATWN514FGPMYPQXMJ2WE9X1V2CWK9ZMG", + "DBSD3WB8GMW0KM9GC01SYZ1Z3XS6RY26Q9JV53VJN30C884QD7T0", + "1JF7G1HB22535Q0S6NPKAZM0P5EMZXS8AZNV3EEB890MJ1V4Z240", + "5114JWCVGBF0EYZEEQ9EDV8J1ERCN967593TE8KDE5S259EA8S8G" + ], + "h_age_commitment":"4BRKZJTCHN58Z1AZ8QMNHK7PMSXDX2T7TCHVF4G0X6ZE56220NS0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"6J8AJSMV0XW8M6Z1WGPPWS45A0C1T59AE09XGN2NNHW2A93G9W9E9Q5AFXY18DT8C7CBPMHXGBVQV6FYC91JS8CV45SYCQ0SXZ9Z22G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"X605VEC3KHB28HZ8DGM5ZHK116NT7FHEDCSYM0MH5KQA3TB8891MH8MWXFAHV00X8CS277Y6XP86V97ZHP098B6ZD09JMAP8EKVR230" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"GHXD9677BHZYTP1JRWTAV2CNKNWHYZQBXS0QARTHYD894DK4XYQ5H2FGPKQD842P4ANTX7AYC66NKRAZH7GG71F2P58BW8W5PW83230" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"AHA0R3SFJTEHQK539BGJ1Q0EFY9Z15BZMET4JESKMMB4A78FFXJYJWTRRA8GENRB1FS6H22VMJF85BP5D7ECY0TKCJB5J4C2NBV2E2R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"4AKJ1MMCWR7ZX5ZB5FE7XAPHZV8VG7PAFQJKJT3QKVYKZ9E93F2EPKXN6VZBRH4ZMCTQ9XNEEGRK731YF2AGM4HJ4TBHH26C46PCR1R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"TFGQJDAY36DY1HHPXFASZ10HH9WBSPABC9MA8ZGQJ9GY14NQCA0JTVVADG2CVKZXNF5V5NM68750AJT1V007RTNN9D8X0YNEHQ39A38" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"65NKMCX22G1VEEVS53WYR5AP80QCWEEXB43YQW7YBBAWP8PB5QKHEGGNN0VR6T1FTEMR19KVGJ7RZ8K9QSJXB8ZZBPCEYMDYTZ0RT08" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"8BMJ57JKX8C3B2VG0GSFSHH3D6PQCXSTE5NJR2D468G820F5JSPCCQDSJN0ZRT8GW4FR83RM8HHFSE40A3DD2FE664JQBW6MEAJNG28" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"CW7M3WJYRXD6FBCBMN6J4BC0J5XN9V1TJRNFGWWZSW92Y8E1WKE1DGMJK0C4ENVHS2505PFVB6H5A0ZWWA3TC5WGYDN9YWM3YZTNE1G" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"6JJRYDVVRSG0YJPZMBZXB3X396HMCMPKJQFEE3AHGF5Y92X3VDZ9CTWV5ZNAKNDQ1J6F0SM7Z12ZARAFRE42T8PY5VNNMQ0SYGWSE1G" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"ET132B16V4GQ217V1J9JWCXEBTKEDD664NYPXY2SCEP5JNRXD2709XD8W0R93SZSWAN6AKA4K174SWS27D33HN01H6ACYJRCSRMPM3R" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"X578D8AYXWW377TEVSQVZ4SWYBGA8HC31VVGGC13X136BQPJPMXJC9N6QH78V7XWDYA0WZ1C2Y480AAVQTCHKG33W7KGBR79YYTB418" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"RGMW7W6292JV6CC9K4WQ2RGN74XJRJKK37AZ38PF9AN8XCZ5248CXYMBQXHN9GNWEQDZPVPH9RX9ENG7DF9Y7XAYTYNHTBC63ZF8T0R" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0", + "proof":{ + "num":6, + "edx25519_privs":[ + "1E728FZPQJXE8K6509RVCTKP0RSQ1JS6J7HJTJXF21DZ43SD700NPGB76KJQXKBF1J9JC5DQ9JCZYHPJQZXNE6Q4WCSDND260E6YM1R", + "B54G826CH6837YEA6G3PBZ8PNAQ03WQZR3DR76ZFNTTXKPNXCW0KFR49HQP3T90XQECQH7CDRTNXEG8C9PWV3BFZNKWHFD4D9935VQG", + "9DTR0QKCHPGFWBA2FKY4TJXMHNQEDA96K3F00ZJV0QKREAF1FC6X8QCRX9NBWEXPK6RY9QK521KSPBHKMX0VT1NE8X29X1XBVHC7Y8G", + "ZY73GQAH9BQ18TX9P0RW2512BQ2TAXPB0ZY0REN38227B1J7ZR3RD359AKEDGYY10E8619H8Y8GSK5HN0VF0SDDM1NYV8A7374FBV50", + "X3T2JMTYRCMDZ9P463H78PPGYQSDDESJNH9NAK34W263BRE3D80GMNN6MPRHSPDP9NES7QMV69AVXZ5GWYFDAYJH56QX3T6JZWP3ZT8", + "MMSGYVCADF7B4XWWEWNN69AQASE0P0NXSHWXSKA8P08MX02JW43YAVVH582QDK5H5D2YF6BYJTTZQHXGN0QWJBVAG187EGD71GRNY2G" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "5WMM35CW7CQTDEKYG8692NDH91WBA73ES4SC6CYA1VC82F6K91C0", + "TVNYY8EXYSRKRG9GRQBFV3BYX0D2A1YQNR7TGC10RPD1QRZ1M820", + "1NE7XM0S0K006DVQ7XWDW4ARGNMFTWVJGHCHR41G6F8GCAD36YC0", + "Q5CTZEZ7XHMXQ88VMP68J3ASDHA8BG2WX16XEME2DSPH7KSBG95G", + "YVZW4RQFKPMVEMGNFKM0KXZD4GZW25VX7NNW7EA1ZJJY7HTSZK0G", + "1JKTGA14ZTEP3ZMJ2HMW14RNGEEZQVDYMCSCNNFZSVTDA5VZ5ND0", + "NYCNHZ09BETJCYBSGM3MG1B0GDW8269SXGQRDFHCQH6RK9WK6FC0" + ], + "h_age_commitment":"3RK14CY8Y5SV7832ST7D0NTM160J4MXWXMNQH7Y06JS1Q1DC60KG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"JXS46EQZ6EHS4METGEXV22SBKVK8YK9SW5DM3YCMKYF8NNNRFFNPBEN89YKDPJ6PJGQKBDHD6PHYW04HMGR8CPW39BY71K152R9GT00" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"8PTRQM6DY9H2BZ9X15VD873AKKM3SV0YQEFS22J23WBXWV3RSMP524R5G0VZ114XGSGYJ66Q1X0PPE54ACYJ4GZG6WJY6ZMAMP48T0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"79CTBS0WJP1MDAN3MMBAE30050T175Q7N93QZY5ZRG1HBJ3SS953TMC3RRWGYP3B6M988YWV6NZVD9TV8XG16257WSD6SS7GB8C0M3R" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"2584W5EC5A7N3N672NPS72551708YBFAQEDDPYM5EWRGDZCKHDJG18VS4Y4X8N7Q87RB5R3SGJAHXB2V9WE7DM6XMHB2E50C0XQ2G18" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"7NKWV8MJYERPV4CVFJTQMK3PDQB3HH3X3J0VWQN1ERMR6KWZPBZF2ZR2QR821D1E75N0YXF1XMW0RZWT9C6TRZGDQAZ29XEV0P86M3G" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"C3MP8KC046TTX8V096G57PZMCM6GCVD16VS2J2N8GQ28TGHN3CCS2EXQ0ARPME1XCT3RGHZNTC1SHNR0G048J8JSP8D3T1X0JEKQT08" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"T6M4ENE8DSEBQ0CYXPNWVZPV8BVZC48D9QH71RSV9ZPMDCVE7YG7PM5FTQGH4RVMDW4R0K103T4Y0R62TGJZNFTV66MEHB2CC57RE20" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"9CZTXABTB813BCWQKN4ACSET441PF5D2X2R9T3V3TY010B5DV84RT8PXR60G35PJ4STZM5JP45KQ29PQH47JG195NX76GEB6E8P0200" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"Z21YBNHCD2RPZTEX489PQ7TFTDR5H9EY7X6R40G0Q0ZS6V899YT4JHDE3Y5CWV8XFMKQM2XPRJZGN6KNTP2R1TYKSGR7P12H1P81R0R" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"R50Z6D5AH6FGQRD8V14YNF62KKZEW03GB7ATAMMVJPEF90KPNTQVCEGTNT54Q78MW6RJDWYJ80NVHWGADNSEHWP0NYATT1J6NR35600" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"9J4V0M7DP5TYBMF0JETYEZP8ZH2WZQVCPR3N2FPN1E8KTMV993YS8KQKBHR6MPVKCC17VSSS9JYQWV6A8AY5AY3NWSD5ZT351703P00" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"BKJ20VJ4W6P6SDGR5TN25WCJGCYRJS4HETDTXP4V707R1A8BHQPND33KHSKWEN31YKD3X5HKT5WWAE2AW00439K9F3A4FA50J92WT00" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"EPEHC69CXNGFW6QHBVTHMT2XFMK8SS6BTD5VC8C4YEBPB85YRM53FSMG20BR68GVHA4RHGNPZX2DTSZKHE93HV4MWMQXGQEQ46H043R" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"G4BC8QWW8H135R5WCMRTBB5ZNJ0PBSNABMQE8Y5M6N81YCCE65M5CZNYXZGV7TQDWB21V7HTJSZ9AEMZKCDP1ZBWXGYA0VN6NT0A0Q0", + "proof":{ + "num":6, + "edx25519_privs":[ + "KPXHYY9D3923BB3K5Z2ZNCENKXDC9V5F0Z3KN39HNTZ5K4N9G02H3HK75384SK4QNG8KMNY88F1KN144JGGW6H92WX2YBVNAJAJ0S5R", + "0JBZTBS57QJNYHB6Y50K9TV0BWFZ0T4K00R5H724GRSE7N0CTW6FPTCD6Y9S3W0KDM5Y8YDZSF475JFZNM24C5RMBGE1VRST279PSC0", + "JVMG8NJZ7CPTBV92WVP17PDGPHDFMWF5GQFK3BM3PZY9HAQZG8014NGN6ZZS5KHHG3WREV4T8KDDB0THEYP5R4FGFW73TG4SKAHVA6R", + "VRM9MVRQKBCV7N546AA8CW95YAXBHH4MYG6E1DXM0QMRCXD9143NB6D4S6RZN1JCKMAGAZ0Z7BZ44EEKVRHSAYDQJCHFFC0XHGZBWJR", + "5TN6FF6ACSN66G564NQFW3P1CR1PS94D05MW5JXWVMDCHV5K8054BM7SJEN6YFCSRCMBVA1ZXE0FEK2CG06N4B4APAAEH6NM0S3GHH0", + "0M8NC1MX08TJT8W7DSBNFNM9V95FN7CJC087G4X7E7P3AH30RM3ZGJ00BNDAC32AKNWJBKN7BGNKF7WP3VQAT53NWKZJDJZ2E26ATM0" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "N7Y1QYAMSEKEQ4EMF6SD0VGZHQERKGZSQ5N8Y6JD4ZPKVJ1QYMQG", + "R1EA6V30HKNR2Z0YVRQXPN6DZ5SXF0R2KSRZ02Z58D0TB282NWJG", + "0AZPK2MZDYPC16DCGPBTJQZ0HRMTYAEFS82KSSZX4YE3Y6F8RJ9G", + "1G9QGRED2HNDTM74A6E2QR4SCG87DEJTRBFZ80A0NCXEC28RXAA0", + "WQA7BSDF6HJ14V6QTMQ3GXPPC6YW23RYPJ5ZFRQ0SBNN3CBVHCFG", + "W7XQSPF9C0PQZTV15XC54Z68ZBT8TB3MKJE900VGPYXXKQPVM030", + "QZG0YM682MSE44VH3JX5AK4GZTR071C40VT6K99S9D6PPZGKWJVG" + ], + "h_age_commitment":"WNFYQQ2D8MN1HBGKJX63YQ4DVQXMGRWQZCHXK0X3H3TACYDY90BG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"V2RXW3MYZNGWTSX555AXF9B34FQSKPWXQE5Y8DYTF33C99PRYBQQFBPYFA9FWC9QKSYTMY273QCKJ4CT4Q2GWGFR3QQBSTM1X4S9T10" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"9DJPQC1CDFW7YS7CPQFAX053C1DZ92JFMKR3VZR7ENN75DBQE3FC2METY3M9WEQQ050506JZ3AQSDNGG4YV28A0QJBHAN99XY34DJ3R" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"98X25TF1VDMPZ4QKJT5GXNGC5PSTEQWKNXPJX0YXENN7CVZZNAJW6V9NE8C0XJ4J1E0J08CWB8XPJ6H7Z80P9DDY5E4S3NFPMR3RG0G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"DE9XK0F6KQBNFTT9MHPGY2WJ0FYHXXQDE517X216F3PR3486ZAHAPM7TMCGVXXQQ8YRS3PT8PWB3WM29STYBBZ6G3T9Z8FWNRFN6R00" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"VGQP6882WCJZ5CTHJZ5SBAHV9Y2EM49RCT3GVZYDCHYH07ACHTQDXE08Z8XK4PJJ4CFGG9E2P6CA3H93A3G9EKGZYFQXFN88QK79A0G" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"NRPWKD2QFBP1WEWT54932ZE41Q5F8G7P4CS62SQAWQZ3TA8JDYQ4GSQ5A1J3X2FQSC3B7SZ3Y7ZPWRV53YKWHWRY6NVSR031N9SXM3G" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"KTWT2DKEXP7C5BFD69YNSJ2XBWN5G0Y80XEMYXNRWE09RKZXGTBEJWB0J0818ZQGK1BP4BXHBD9ZWFDGWR7FFMTR7EYMZC92TWH7J1R" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"J56HFW84SQ6GE648N230HD4TYP6R59NYYQ7WKW5B0AMSN3F8C2PHBGQSZK6YVM52H4T039W92MNGCK90Y4CZG8Q4ZMTBXY7BRE51628" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"59AK4VFTGW3Z8RSTWCFBEREYXNFD9J60CJMHG2PRR7QJG08Z1YFSVQZ6RHZRXSB3FD0FV7DR577TSXGH4CT76DXCFSCWNQQTNXEJT18" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"RKDWQWD3BEHCS4E587M5RH2J0N415Z378ARVDGP08E37PPACP31P8EXMZ18DGM5F40MFH4WBTTN8S8ZWR5G49Y1JHKQWWAHZXH6X61R" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"8N7WBS85S7HGEW92ZR22WPD478RF133FHB1XX1CZP36C2XWXF9EVMSE1PANJB382QVV7Z53JQY2YVDP8MRJDR07G7SDDSVGXBJQKA20" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"NE21MAKYPNVP3JDXZFZB57BFP2HV8QYEH80Q666SZ8K9BRXJF712S909QFJ3E52Z6T1CNVXZZEJ08ZFY34NKG1QFNTYXE00XT0ZQT3G" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"QVAMKAA1Q93C3SXNTP1751YAY1K480BVS4Q9RJ0QQCE9GFCK3ZQ80TZNJG1XE74XVDS9M36NJ8TV7DZKAQKQM3E3TNBAKPBP2GB3J00" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":20, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR", + "proof":{ + "num":6, + "edx25519_privs":[ + "T0433VFG624EPQH760W413MAPDAAVYEDCADSYC5TCT5QZW20C1901F2DXG4V4WT9Q7JC68GRM1NEKP42DE94FPWKSCA5V5F6733C230", + "J03G8P6DGAKEGKXM4Z1MCNSGMMWXS5HP95AJN0D6A2WZ2BAMSH0ZEC074XNJQRXFX5PJ3H4HXQQ392P90E0G75BK6YFR219G8KW14X8", + "V21CC5D214KREN4K0HTGBGKSKK65YGVCQAY0RSYQZPVMYFH4EDKJF5TT1G0QP0Z6Q0X6HYQ450A7G4E8CXYV5FNPY63NG8KTDYVBNRG", + "D1B1ET040QYQH77QPAXXRENV27H2R77JDTR90C0S0P8NC3ZPYNJ1PCWJ368XJZ730D0Z8J61RY2YFJVAYV14ECN9R7JRMHZE75Z8NQ0", + "P3C90JF84DCQR8D15QMWQJXWD0HPJMJM4EB8SQ3PN1YGY5ZG1SF2GKVSPJF0MEB46MFCJZAQ690RX3SHB477PECY46VGWVEHBRTX6HR", + "022936MHW927QM8JAWTSF0KK292P0DTNP8GVPXXKCTHJDV89D1SDG0Z8RW2PDRH1XKV3NJM9ZE2EDCPJ7DMYGA35PRGQSBDQD41G7TR" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "RF4P4WMXB85H2N1BH0ZNCM3R2BSKNZTQHMGTWDWV6MN9SSQWG4P0", + "CHCVYKX3V9CT2N3THWY13M0DZCKZRQN0D342FJ5CKE8YFN6GEDBG", + "SFYG21A8PVG0HQSXBCB0PJK0SRP1SW7E1E5Z8S9DM8K8EGMD0E3G", + "GFFZJE4Z4AB8A9YF4CB9BCKCA3H3YZCJF7ZSNMCTK9N8TJQQX0HG", + "0PXVZYMSBVAYD2R08AMN87KF9QJ3JSB9RAVM1TJ5JQ3DYQCAPJS0", + "6SWZZQ0C844430KKTEBAHPM6N9HEGDPTRXMQWDQSDVG60FBPRKPG", + "03FES7WC1YVMG6J4YB5G6R2JVBY41F4DWV4T5ZB9ZBYWTW5D09DG" + ], + "h_age_commitment":"HC78197HG8GHRXMPVD2SB7DNWVC7CWY77NP69CSX0WQ23CJRKP50" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"Y7FG6XFK9B7H04XB1NGJA1CWANB4DATRPB9J1J4M4JFBKQNH89YZH7VVE87GES4Q0C6G326Z0VRNMDRSAQTXMN90GN1CEKHKZFPKM08" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"QZMK2ARJYSGX3DYJ5XAVJ1TMGERQKT2DHQBQ7CN7826SSRV44M8XPBEQ9655FDDKE8WNRHVKYP4CD26XD3TXMWQSCP3CTE7AMMQWA00" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"1C668JPJ4PCBQ0VP3PRTJS00DE43J3MZAC1NNTZPV3VDQGZKFN9D3ZPFKCJY3GAY5KFCAYPT5XCWTSQC6ZC1R7DMJS5MKTD30EHD81R" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"11FKMF64YK5DD041KY9ZZ7DVW6EWZJJ59PFEEZ5B62PTK5664VD54AJ2GC3PCEHSERVVPQFXQFMVBQ3B8RRGFNHNAVA8EW7MCSQN82G" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"TNQD495T4EPRVX4FSQVH3RGCEBB906GZY0491GVY7K0XS2MCH5CCC3PA1YRRVSYEPG9XXJ35PH58T0R95R2PQ5SFNAMYG7Q89YMHR0R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"V7GABXTSM6JE7EHMMBXTM3XY2TFJB0ZWJMV3G5XGSKAHXEEF60XMCXXB41KSWSY4Q8RWTA1QYYZTPVZCN70XP2RSNFV4VCSEKX1YJ00" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"366C8KQ59H38GP12MJTZPA3F43HXCSGBDX5YJQXGEPGZTR9S71KV8DB8E146BJWEV6DCZ1MB0KM64RX1GRZDPTQY0NRBRA81SESMP00" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"589C55MDPE1ZVN2M47DBKWYGJFQ8WV383N9GN9VN73Z5D2P0S8D0X58AMRD933DCVJEPYBJNQFA2VNGVX20WYCNWH5EYXRNW3596208" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"GT64FP7NG7V44SCDH7CT7E3V4BYZYC1HJSM0RC9PQN3FEK7XXDJQ1MPPXTDR9HV9EGXSKVA25JZYCXM7HVCFRFW5MHQ6V4GEXTHWE0G" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"ZD5RHCJZ60K9RPYASK5TC36QCYE75P3TNGN5AAK9WME3GNPNS5EHNW1Z0WEE2G9GAXST1W5BFT20NN74P8DJWPHFRVGMPVY53BCNT00" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"8AWERHA54T67X4AW28FXRKSTSWNSHGVTREBGAR043HTEDGMF6MAGM6HJ5GZYMYH8BTJ60TJEQHWBHKX50SQJ62CDAY019BV6VCBEC1G" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"8GYJMNGPPK8G2H4BR294PX3WGE3W9CZH69J93BH6BMN5EJDBHAGW08R223XGF4EXATH85WY2C6QE3VBDBHE8RFBBW1BCN4S8FMZK03R" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"TF7G69AEKRFFMSKQT5Z1BKASQHGPAD7G8DRX9C1FYEEW64E45633QGCDFWGCKYQ9105XKZN4V6G91CS08RZV82GP3Q6F483TTTDCJ20" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR", + "proof":{ + "num":6, + "edx25519_privs":[ + "PFV6XF8ZABRPJ0YNR99P0P5BARPDK5V9TSG9YCJSDBJSDV7YG8308SF6J7JK84KBBQD6J76ZQVPQ938KVKTPG02X8P2AQRKK8F5PN7G", + "P33YMR1GHKFYFQDDVMD71XJE78M322AMZTRSPANSRJEEP0AKAC3HXMFWVDMZTDNS3Y0G9R59NPTN77FY8CH2J991G54070B51S9R16G", + "F41R3MYFRB6XKMY1ZVCHFV6JK6JCVE510Q2H7645ST2VFGBTKG639TX9FE767XV1CF56A8J3AF79BW84CRYEYKGSN0BMA73KPHPEACG", + "KP7ZTR2A486BC95H8YFW70T29R8GE08V55EZE0AADHDSRDKASW32ZDE4PAZ156S6BMTH0X5S70KHMFWP0WFTKDVMGY0H7F0DK96N6T0", + "DSVSZGPNFRP90W2D6204T28F44DY0ES8N8FE2CNPFR49BAV5G821NEAWWNRJDP2V7Q7QF3C8PVWZA8PAG0DP8DHBJ0N53WFCPD43SA0", + "4CPZNJZA5B1G6JTC57C1EFK77FRQ0PPQH01Z5NK3M3Z0Z8MJER54T0EFQC79YDZ0NBDZW9YZAZG85WFCQSKCX7DXCX3HAZRKK119CF8" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "RWF6QMYDKRVRWTMXM5BBJZZ3CTTN8PEH957FJ5CHM5K9CHC8CSWG", + "3RGDV1XQJTKQCVXCACPGWMV0RQ44RBEY8NAEXS96Z2GMXTE7ZTE0", + "XF5B2CYZ2PG5EKH3SH95Y813JE9KPVJZ5SH8S7T7W15QPGEXAC80", + "20RP7PJPK9MKM4BWG66QQBC4RH31WMYYVPYWS79KYAS41SG1ESF0", + "M02VZCB7CB2DJAESMMRTE35C12DX7ZS7HX71WFRVHBZ0MTD1X080", + "R2ZRXHVG21CDR845B4MTFDZX3FA3KG4A5W97WK2Y7R7FRRGFBV6G", + "N3EW91XK4A3AT17QVAM84BFEV2CTNQ3MH097911ZT9F4Z5YDHH20" + ], + "h_age_commitment":"654SMA6G4JWE0CEH1X2DQS7BE9JRGQ9GXX5PJZXM507S72N4V7P0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"0JF0031D331NK6WBBHB3H5Y6347GB8EV72VM14FD1FY854EBQ8Q5JF35ETVBFPATAM08TCGFM3ZJ5EP4871PSHMM6DHVVQ5MB8GC03G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"CJQEM1XSVDXHM5R6MRSBV2VEMX92HPD0NRK6C8WGQ41733ESZRS2C3H02RCJHXC5A6NK5W40H2RM00FBRYGGCGN4J2M7WVEKMXR6A1G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"83QA28NY9M9K2JH4BPGN48D25Z90A5REA7DKECW0FBN9HBXPVAQZXATKQDVJP472KPX6ZV1DCF39CXTYC47KECH8T1GGZTEVSJXA01G" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"0GS3J4SETR0MR0WD3C59CYYC1PZZT2CVMCGKS56BRPW63HCY4CQJ0M2G0QADTDDNW6TEHM1FG6QJDJ4QB7TM1J1TNJKHCQ4Q8PBZE08" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"Y33FE95QH7EJ4M6D4H4VNQXDEAW59MWDE9H7PS09S81JNEF4XMZAJR8MCJ5S9HHMRM9ZQ1SCPEYJQ9K1X3V8WB3VS46RBMPVZJYQM2R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"W0EHPB5A7K23099D2E426S8EYMCCWQXGCVXDWC9K3RFTSQVFAYWCHZ5SBYRJ52SEE33EBNSF1GV0XHK79BR073HQ8W8QDNAZ2QK8A2R" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"WYHY73NCHN1CQ9JX4C1DDP6589H66N9HRVWCQ4XGV8TQ76B8NN9CC6WW7QKQWX7KWSH9XXY7BQ9M62NCHGG1S159DJ8Y31M8PNGBC18" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"B8N809D2V8AV2TE1DHS7D7593TGS4B67PV2TXKECHRZSGDW1X6DYGWM88ZWC1791SK6YZD2Q9ZDJ2X6FD9REAEBWC7BAYF0MCETFW2G" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"5MN9X1Q8GB19Q07EK9GMMWK6AEK1XTZ2ZW8T2VP0DEK0E0Q5GSY0KRQV0D0SNDP4YKSKA5SQ63Q76X1Q1GE6JZMCR2YED5BACKSAP1R" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"9VZ5R4511P8G64FX8C27CJAPCE4R8W12VFDBJEGMFTFZM5FYZA35JB1B6ENZ3DS3KVPSNC43JSR103DE51FS39BEABWGJ8EDBPE5P38" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"HQKHSDS1FD4Y9BXT6A0FNREHZTYYHAZ9586YQSVFMH80PESE7FJGM1HG750Q3H6XN7RVWXY66RH2GAENYZV5AZ455VVJEPCKJ8JG400" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"Q4QS9ZXADHNF1HYTF4N5529DTJ2MQV7NNBRE26487BG7R20RFVTMAGQ73YZAB3YGRM38VR1K373X1DK9KVCGVMKT3RAAQ8RMMCK6T3R" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"DPB7EX8D2VT74Z8AWTFDJHT5W3N7XQQHZP7KJEN3QM9S3G4XVVP2QSZJXWYJEFFP1A0NFGB12A4S292WVBGZJWAPQ7YHXN7D1AYW818" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"V224D79TEB8S96HSEQE5D14BF6NCS3YWCBTRN65JRK5N3TDB7SNGMY2CQM1SBM7VPE0YKPC448HY2B5YRJB7NJJY3V2X3HRJ7RBKSRR", + "proof":{ + "num":6, + "edx25519_privs":[ + "7V3WJZ7854SY84P6QRGTXCA8ZK2XEJ9DQE8EVCCH9KFKJHRP546VMZFV8P1Z7H54H23WRK14ZPWJ0KZVV07KC7272GR6WJ9NDS8ZXH8", + "Y7MJ6QNKJY02N67EDCVRG9ARW9RETS9R5T42T4STE2V4TKQ6Q829WZ7MFCRXDVYYGYTEMZJ1WH4AHG50W2R6D872N4H9BTG89YXTYVR", + "J0YAVF4DDVDMHP6DFFWKTTCTED1F950P0NGNJN8BGQ5MNCZ6Q423A4HDA8E93RFW8XG25CABBC15R49BCEP4TYS09W4X83CZZ5RNQGG", + "26JKZRMF2ZY6NXFGBMM9M8RSNN3QZ51VZYAKB1XH4872SP2DXW7J9Z34S4HFQDD5PQTZVV4X59D9RBKEXJXH1BX3PBDT9FSD5DHMXFG", + "FWPBNTYBWTXERBTQ2WETE3S3MV0WSYY40AXEXY5C01BDGY9B4W1ZDAZHSJX8JG0YWFM3EWJEWVWV3BHTKVJG688TN8HS5R0AM7ZFG08", + "4T5SJCX9H4MF1FC703F29BSBVS7QS2XK4FN7E54PF0H2MPRNM057D2V4DM5TMBJQQPAJT70PT41YS90TE230S5NP70WXGH40VXPVRR8" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "1WPHZARQ449QPHBW81ZKSB078ZZG4YVV5HH1VW27PE1GKPQBPBPG", + "W4VF9EX174XNS0RS5JBZ485DS715PBN6K2D4HRM221JY31V0TRX0", + "GZXWZQBWHQ1DTJNYFJ810QHEC3GKVX2EMA7P9MJSV94ZP58DW0GG", + "BPVAG873KKQ3JFZZKDX0N2EWPD480RW5WEJC4W5TGZHS2E4DT4AG", + "5YKS0RJASJS5AGCQN24RYHSBQ3519B2VPTP7CXH0GD76KESX3WH0", + "9ZGP8WQMPF3RNTSV65HKH0RHWM6ZAM4CM7PR108GN2HKFNKR6BYG", + "F0ERN7PESSQADM5V0T4X3KQYVG2A5MJGGVYHXTKWZD777NHFCWT0" + ], + "h_age_commitment":"C085K5AJEV9V3JAK3VYY6AQQVTGPWJTBWK111JPQVC14B2113EA0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"K6KCP5WBM7DFERG4MFSQ6A8EJ3YNQ3R3AC5M2SGSNPQ6NQJG2YVQDXQQJJAJW5RNAT7MFMNV13Q25SK0FRXQV5SE3GHQ3PZ6Z8ZRA28" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"BMZ1WSPTGR2P7JB47QDTDRQXV0M9SJ1WMPKTF7C91NP4RXE2ZZVKDPB87R8QVKWFB4V97X53294Z3BRRE6DY3Y6GF6C2MX9Q123B210" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"CJKP66QPS9QG1VX597JHCXP97KSRX95AC67F0V83T3N4DRYW9WV83W4Q98TG4ZQVY0MTRHFPS2ECEMR3P708F7GKR77HFYDWJKJV030" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"SG8V8S81MW1MQ8HQ2SFBWKGFAQGQKFN4H3ZR8YDKMV25PETWBKT6H6QXSAX8JVF21MN63XZNM4DCBCS7J9P10QB245M5WWJRW063A08" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"4NJFVGP5QMR39RPW5S32E4HGS8S9HJ5NT78NQ6ZPHHCH7G5DXDSH2Y4039QQ1HS0ZMPT94VM4E31WT68WZHB38A4DVW82385RX2ZA0G" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"73WC7G8KZNHTSAMC5CCRBZ4PV6S9DS8CFT11GQ5M5RFPVXX2J18PSQT25Y07M07YFRPM9T05SVM6B3H87MTCVCK1N0T4AAJDP1D240G" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"9EMKVHYCCPV23KM3HCA1S0S69N9X6FS78779YA0DWFDPEHB2P7FBA153ZP1K14J0BPFWKXXSX8YEA4A0W6BMC6967G04TXF6S7S4J38" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"8RS9B3Q9RYFBNTRTEXQ1N814SK1BH3QHDYWWMSCH8SG66FBVDVD4KAMGB4FCYE93DT4ATJSH59J2DRCM114E2WZHE5MVJ4P0FPY0W2G" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"G2JG30E2WADZKFJGZE4PHBNXTC09PXGEND5X9NXGYR4R1HE4XZPAH16PQXPCNHBSBZQZ4RBJTC2SKF1P6M35XYQZZ0FH65REPB9AJ30" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"H2742HTNWVBRJ36X3GKZJ9ZPYXT5CC9RXQ1VA1D8QG5GFJA63S63QVV6PVWJXYVH1PAVSQFV0WFJ5XDJZBHBB4AAE47BWHKGR7A022G" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"VEGWQ4CDZJSKJGBVNM3KE0P368F8SE9VWTV7A9SF5RJKEAZZVBZBENEPKG6C4PY03W837RC3GE1RAZNDBN4FXY323HW7HGVHJ09QY28" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"CRKZ8CT8ST7Z75EZPEXN4YKX4VFGKFTR8JK66AKT2VQZ611B6NT9ZSPM0V9WK0CCGNT38HM8T15C0CK9KNMN5840A5B78DNFQ2KG81R" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"S3TKC9431PQWSTB1DK5S73W5VWDGB4CDMNND69Q4N7W88TWA5ZCGYR058MA838MH4PVXWE8T4HQM4SVG8NJTSJKM84S31TV0PZ8SR0R" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"not applicable: commited age too small" + } + ] + } + ] + }, + { + "commited_age":22, + "commitment_proof_attestation_seq":[ + { + "generated_by":"commit()", + "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G", + "proof":{ + "num":7, + "edx25519_privs":[ + "D3J5T6KYY9FPP3T8ABW6ACKJVVPSCPSYQ5HQX0SBZ7P4AECM91M8YSXFAXN6304AR619DWNB08YJNYZYWZRWKDWXSB60N276VF44VCG", + "T09QZB95TX0S5DKSTXCMHA7NFDYYHA5P8Q07775GF5QB6GZ7WN55SY2FBWSASESH0GX9SK6NFWET27KSCJET1EKX66NMD0QW3HBEC4G", + "N22DV3PSEZ7HZ91MEGHWN0V82A1A6ZQ76K9EENY4V7M5R0QT7SWBY5JRJSJ1SPVW6W96MJB39G3J1FCR8MJ5TEVRKYYP9KKWT7VKJ00", + "536Q6SS1D8GET98R17DW1J88YQ9S0FT6PSMWQ6VWBKM7Y5SSED2E3JGRMT706SD10XD93S5W1Q0ZY0C8RBE78QEV3YCGEJ7KE1DGPDG", + "R2JSJ94EVX0DCAA4GNJVPADTS19RWRC1SD3Z93XJT93WPWH6HS1YWQ6D895DD786AKWC4H1AMDG4V544EZ0D8N6NRZ0TJBZQF47NZDG", + "A0EHP1ZF4TGMTJJN2XWXR6MSSR593SCMMX21W8GSBAKN3WQPNNVZ5EP9YYMCT3X0FKW5TB0NBPCNF2D0JSERDTVFNJ3CCXFH384DPZ8", + "Y1P3RPX4WH064HBQFSRGGCJ4P4PPN3T37P8SWTS1X4BZXEQ3Z11A7M81MBYQHYCD5FTVKK31BT0C2C7TKB8G9K8Q0JY73TH79W70690" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "M5XEE751FGJJ8MGR28YXZ2NZ2FB3QPP49CHA51MAT5XDBTXS20J0", + "868HPPPK4AM66S6W8XADWF3PEGB0PMSJBYJVXEXVJB9JW158B430", + "ZBC6F8KRFZNTSW2Y6HSH7NWFCYDZFKFQW02E5BNH49V7T4WQT980", + "ZJWDC7STZZT411RWD5ACN970511J2HHPCXHMJ7QESDXNWQ4MJGA0", + "KGCKFXY5AP8C3F2G2YVAZJN56B5H3DSFAX4R8VK9K3QSRV9JH3TG", + "VDB9X20KPGA35GX5MPEWQR4JH6BX24BAFH3RSJ39EGF8F5W43MT0", + "280VG77EK80XDN5N3WBJA46GEE6GH0JN7DQFX69MGQF9E1426GG0" + ], + "h_age_commitment":"8MHAR659DX9SBVKMXC896KDT57WAJPT0C63A8R82TW9DRJFCK0TG" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"E12SP7J3MM5N9KW7BA77KPPFBW5Q6JKPSBDMT8YS5PDH48TVP5Q5S0D8V6RMFQBZ2MT5TA5GPCK34M4S9T0RVHH43FMWKWADG57Q028" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"GQQJ02JGXQA2CF8FDWZAMP6MN98RQAANTZHCC0KYSDW1VKREKJKRQRHJ4J1M8D0Q0HHDCS16B3PAVQYQ9P1425VHEW34EK3NQ98RP0G" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"KTBBSK80END930HWADGCM5ZCZHAANWS923YQ99AP3EX26ESVJ0C1TMJGY1J9R88TV28X37VZS6EKZE1CVEAC5W6PWGRCWRY9HJQ8618" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"TP9FR6JAZ6H903M0VC3VE9K9TXDT0Y9FS88ZWKCJEYG2FA5TGXY64ZC3W9NXAJDTP9B0WCPJBP70YWWRQVWM2ZV0R3XTHHNSZ13QM08" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"E7GZP9WNZYQT8Y5M7R75HFHSBA4EGP5JPPDYHDX1V3J5HMM0B56624E24ZGYEEHA20H0K4CG54XVKQ66BTXF996ZB6WN9Q4CECDY828" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"7RJGGR63VP135SCDWWF0MKDWYN1PNPN07MG4AT3Y1AWBBWVRE9FBT7DVJVKWCTHHDQQ342MEGRHH9ZFFJHE5NWVK1F9VVRYWA30AA28" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"YGGTC4QHVBNV135QEC1B3ZY0T8985T00NNTYBWPD7654M0195AXK4MA2SP3FKFSWG20NZ32G92BCHEQABANSVWF8CXYKV341EJZZJ2G" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"DXSQEBY1GZ6MFCFPYT8E0ANCWH0ZBBNJVYYNAXHPCPA9AA4ZT0KKV51KGV5KNRP5SBPSPMBFW2Z50MGRGRPWN78CW9NQFN3B23VYR08" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"MQN1B5GD7SVP1BWZQJD72KVZPNV3BM8MM15886421HDRS449TTN5VEWHF3QGS6ZNG8S4B3TE1S9YR3JB1QYP3AZVNZ4NNBA8DC4D22G" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"TS2Y86R0C6TD4ZB51F0T3DQJTY3W2FSZESZQJM6MH30WZGWSB4XMSQQZNKEDTKS0G2EVYB5P8Z3W6CG00SBA0V9B1VR2Y4J8S797J08" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"31X3J17Y4X5EVPTXKAPZ2JN0HRTMKFVG4HV0DTCGQ91B9C271Z48A3GBN2DSSSC9PHT6PGAXSWDA595MN5SXTV22F4YZQX5T8238P3G" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"NB187D7N8H8DJ312DV9ZHKX0E71BW8V9RQPGBFSQXNFFK6SNAKQT0F2QV2MB3NBQXJFW5HJ8V0ZAFZ7WBZQ18AJ62AQC3BX24SQHC20" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"E1RGD5H2BSZW63P4XTZ958AVB9055SE1RTQJ6FET4KYXS46ZGMVPNR0BT50PFHQE3D70KC4HF3ZTBEJWX9TNFK3DBD1TB639MFWZM00" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"R6PFM3BQPQRQ5PW2D00ACDBNJ4YSASH6X7D964CXH5VGKW0HHY0WZ9E19D1VF21KR99N6GQZ1XYBTJKJT3WXGKH8KA5HHQSRGF5PT0G" + } + ] + }, + { + "generated_by":"derive_from(1)", + "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G", + "proof":{ + "num":7, + "edx25519_privs":[ + "48KWV9WN59E1E0VM8YY3JHVPEH4J2Y70RS9YB2C8J7GAJWJ274562RCJ6Z0K31WQY4QJHRZ3VATV29B497PYAK3CXJ6B3KGNC623A8R", + "7WQ379V40AYSH6R28PK9VZBYM7AA5J98R3VRNRW53T7K8RG7ZM7GG7N1Q752JEQS1789R3VQ4W83MXM0A5ZS2N32QBVK93A2MKZ7ZQG", + "0FWF6RGN2YCERPSG6GRRA5NKVATV3S93YRSEW68RD7VFAC50WR2JW2JEZM9SKMYAZV5RW6EWWN7GTHQG03VNADZB4C5K3MFYNH1WSNR", + "V6B4B8WR33NNHTK41V5EZ007S2B2ZMS5XBP83Y0F69341A7F8442NJZWXFWKWM7EENYF0S3ZQW2N25CJDXWFHXDKYR7BBH5TPTD0110", + "5W0NBZ349WCT0NPS350MZ7J4XJTZQDH8JG1WTEQ0ZWRA6KJP38084B2818M1NNZGEJVAVQ5AN1ZN4BPB7NCDSTHYA6BM7RFASQ7KWMG", + "227K374E4W1NEQF00T6XMMHCESKAFTH0XK2H2BZKH291AHS25R07PESSM8GK0HFZN13DDQ6PJB4JQZRXMAP0Q0216XJ6MJ0N6PEHKTR", + "0ANHE9DNND0CHTQR915384MGS6J880R4055T9H6X9ZA3VRRGTC4SCKMSR0FT53F77232S3YV1MXEFPZTT971BKEDAY3N0VAQBVJ8AYR" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "BN64FXEP3D154S07RD01YHQV88GV445DHE39VXQ8T4XDF6BQ88N0", + "JZQQBV60X7KRQA52KW9JWFG4X2GPGEMEN4PEDHRRG8355V0HC0D0", + "QYX7ATG18A09PFCPGKV2PCXRWJHNYYJQV73NQYF9NB7GZV953QF0", + "3J0CXV9N253F1BTGAWR32G6GHJWPV7N5KZ04K6KVA774FDWHB7QG", + "2TF4JK194DXDF0AY5PXN96W969EFM4K8NMMNY3FPP0KPF1HCK2Z0", + "0FYTS1HZZ9PY24Y72MZ4JB1P7M8HWJYPEZTT3D1HQ8H4H7GMWD4G", + "83TE4YK6JVM3TSBT6B4SGVG2XPN1GVMVW6PMM146T4DA0YD2ZPZG" + ], + "h_age_commitment":"VH548HRAGQ6EXMDYH6J3PN5N44F39F28W2KB573KMKMVM5E5RJV0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"KPWG0R0X8PAP7CYJS9XYK32TGNMV67FJ5T6DD9004AQ83WKCNYW8AZT7HRRX7D7ZY63K1566ZMASQHERGY6SZHE0B5BMGC7GFRFC81G" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"8XS4FJRH95VFC9H6PRA861CNCEENNBK5JSSRYBAAJ61GS1866ZYFG8J2SJMGYY27ZBA8PV97WME6TM7YEGW5CNFP3JXFWD83BAZJE0R" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"S3F5TC1NASXERW6G1DVJM65G55RV86GFEGJDPD0HJATHQJG3R1M491ZMP264VE9FE6V1D1JHDHVXA1WF1XDTDSET7ES2SXA9X31D200" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"9M4WVEJQM6461Q6K2XYQR6J3CSQK3Y87XXJDWEV6FJHAFHXE9PAFY6BB6PN4R7D6NB4AZ9N78TC2RN2QFN0K7PGGRJ73A2WZ14RQ63R" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"PDSJ1B4YX04W4HWTV269S33ZWV19JK9FSGSKYT6GSBQ0FZ5RKVVYFK5MTC1ZJ1EFWESPZ73XAPHR2MA3MXAJYYG86B3B2SFA2EYVR08" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"M0CVHQX2BV466A03JBHHJ0G675F0GC65P5C58AC32PVWW1Q4JSTKGX0H0B31TAF7R22YM821VQ7XPHTX8JH7JTEZM1C933CQHEA1W30" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"Q6Q4FMWDATN514DZDR6MZFDEVA9FJG58DN6JRHKRVG91V8AJK2V6C2CVAPFKADGJWDXJ9ZK61J0MV05EMXS3S2Y378TS5CEPZDPZA2G" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"776WEDGWA95X8RZZGXPN74P337526KVFSE57JNXQWXPSB4B29J5A57DGS668QHYNXTGBRDPSES4PFK8F8NWMPHMZ9B74JXYY9WBB00G" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"WHV60YQWPX6VY5CKE993KQQGCVPX9DT4X15Z9W6KKK29ZWGJRR9QXDDD7GMNWFPVMF6ZSJGMTEHYWW3HYC2VXNDJTRC63V7D1CEBT20" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"M8KQH3V52ESNTBE66F87B1H6C259PA11ZTASE0MD4CSCEMXHH24KFY6H75BK6C29GMJMRTRJ1DS2GY4WVFFSSS4MEVSM2NGSVVE5A1R" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"360E5TAJWG5X7YQTVMSHFG03RXKYGM89K200NRQ9ZFD800YVK187XASE7FHESNY2N1643Z6Y6ZMZ63QJTN87ZMW6FZTQANSP4RVQJ30" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"E78R1KK0ZN193GDS0HQBX5X35Q50VHMKW0NHK7HQTWEZT5VHEZ5NRRSZAWPMM04W562YF0XHFZEASRMN8HF82CX6JG0W5V63PGMHM28" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"JSN4AY3QMYXZTS293HF9KDB0XVRFBYV9AZXABKGPS53N6GM1YGHE0GZGMBXJQPZ7DQZSJC963CNABYKQ3239DGWRENG892DX3FMPJ00" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"S64EM7EXTBJC8M5EH84AC60NMJX02AE5X9M2C4GB72D8V0RSP4ZYSJA7SX2G2T9FKEBE8KJYNG6B56S6H9BEKASYXSDDDKXPQHK002G" + } + ] + }, + { + "generated_by":"derive_from(2)", + "seed":"TFMYF6Q9WSDMHGXEE0P6720THKZKQFJZDT2VW3Q3M1QRG1DE1ZT7DABMPPRKBNJ9RSGK99WBCX7TJF8WJ28X37S2XDFE2RGW0G06P8G", + "proof":{ + "num":7, + "edx25519_privs":[ + "4TC0H9JTH1XQMDW5KW9JNWTKDWDP64BQ2NMNHXVHBAC0AZQTTG28F0AXKPSJ3P8QXHD7FT09DSRJMS1H3A2SVCE3Z2VW17S1P4EKQQ0", + "GNVH3040NYWGS5H9N5KHP2RNPGSC0YCW8ADAVGSF47DNY9WY2M1KSY6ZH5D5XXV1REC5ATRBY67FQEF7H95EV728MP2W390XDMWHCG0", + "74Y8YKJMJ0R67419VF2WC2TNBG42NYX7H8W0NXKXG1E3ACJFJ81YGQFCGM1TYS35HAF77JFJ7T3TTYB5XFA9HTF07WNAMWA3QVRXXER", + "M7BKSS5XCRMX6K66JCRVJD3H6D2RR15MVQT68FBXVW3VZAFK180G43KVSGDNCVRDZ0V8AK4ZK5WEY18F11T5X00K7XA6DZRSJ14K028", + "27JY2DXAFHS2NNSWKN1MM538RHFR0RZZZ0JE8K78ZACXHYF4742GGN5R9NRZ991G60MRGN7WB3YBC2JA4C645XQR5DDDH2V08J0JM08", + "B2MTEKDCAKX52A72SZZ1VP25TSGCFECC4FBWHDSG9D0HV1EWN46FEDHNF9RT8NJKS77DRBN60ZZ78XHC5RZZ5NKAB4301ZKHSY851EG", + "6PGFK1NFN9787FEQ9KYMBJC7V4BK5TBTYQVH1R210DQK0P6M1R30YHG20D3TJGQD3KHGE9Y1ZFN5KEZ0FEKYY5GR3127RTFCNN8R8E0" + ] + }, + "commitment":{ + "num":7, + "edx25519_pubs":[ + "550TDA8Y4WPDGKQTH2M11TRGK4D503ZGT462FERH6Q36JXC6RJSG", + "T61NZ80SCKMDY8XKAJHBT5E1HCKV705W8MS8SZ6KBKQ6BWS9KKG0", + "0RWAHBP88K9DXKY0T4D32AZVCKHQKQDK7MPHRV5AM34XJ7AAF9RG", + "E6R53MJ1XE7MBMJY12EJ0D44GP44KT2FFYK0RM6Y9X8K7YF4QWB0", + "4XM0VF2ZB2511PKG9AFTG0QYCVEK1FNJKN0R0FRW8J2BA8SBAK5G", + "H3X1A88KSZ143EN7ZYRVF616VWWDMPFCPWR9H8G1HW95EA02NFJ0", + "ZFHXD4AYWA1HV677VQ65MTZHD4GBE22K7Z2BNP7BXZCT9XX1BHGG" + ], + "h_age_commitment":"A4MZ4FXSHK8JPFZDNTRH6QWHCQ5G6ZFMX4NDC0XR7FZJMGRX05V0" + }, + "attestations":[ + { + "required_minimum_age":0, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":1, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":2, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":3, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":4, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":5, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":6, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":7, + "calculated_age_group":0, + "attestation":"not required: age group is 0" + }, + { + "required_minimum_age":8, + "calculated_age_group":1, + "attestation":"NH8WVEV5WTQNN187CV17B5PBH60J1CCKMQ48YP0Q4RPHNB1V1W1G3ASC8NMCK13B2MC21KF7CB217XN650Z6JRZ8X6QBS6S9HREW800" + }, + { + "required_minimum_age":9, + "calculated_age_group":1, + "attestation":"5C46XKMM7996ZT4PE1618MV55569QS35BPD59PGSESFJVBP9QVJVWJFQD5WP6PZFK8K5G6EV2BECT71FYYJZRSCAXKTM3EBQVYC9A1R" + }, + { + "required_minimum_age":10, + "calculated_age_group":2, + "attestation":"2GHNVPSE9BZ6MEAAN36NBR6KAXSVE9WNBHRJEPNJ857TSTR3XH2BRG4WW54JBEVE8X7YPAA4MFXZ52JZ8J8AYM3F8STTZH85BYKQA20" + }, + { + "required_minimum_age":11, + "calculated_age_group":2, + "attestation":"E6AYVWSH9F6NY79F3B7T8E2CG1SCQEWVX540R1W3Y6FP2HF6KV0M247R71S1SK0YT3SKW4HAKBXPDQXYMQBW65H1DF64N86SRCHGT18" + }, + { + "required_minimum_age":12, + "calculated_age_group":3, + "attestation":"CW7MBBG633Y9MQPSX84FS1Y8AQD7F64DG2FRBHC2339G7C2WE0A06HRMHN851194P2M8N81MDWDXJ6Q26A6FBMA58C2CFX561BT7R2R" + }, + { + "required_minimum_age":13, + "calculated_age_group":3, + "attestation":"DZ9D0QSE9Q4CH48WYPEYMV0FAVK27R9NZB4QBQ4T3X0N5MRCXPKYSR9DMW4SS7HWNQKXYR68H5AFA40DNWQC4BAKFZFJAKQDFJBJ80R" + }, + { + "required_minimum_age":14, + "calculated_age_group":4, + "attestation":"98AA3G7GH39SXX00DAZJBJ5ZAGH7W23SG2EYZFYYDTRZF0D840TQVKK663QM17SXWN6ZSJJEZ38WW7CM0A5VDBNZ3BVQKSMJWSBHC3R" + }, + { + "required_minimum_age":15, + "calculated_age_group":4, + "attestation":"BR1HZEBP4F55HR5CY9X5T62Z60PFPEJBR39341250VWQZVEJ58H84P6TTFMS1CS4WM2ZWC67QN742PQ2TK08Z8FR3XFCKVJTDS12Y38" + }, + { + "required_minimum_age":16, + "calculated_age_group":5, + "attestation":"70SW0VA0E9YV34Q7G9RB3ZT6MV99PERMW488HCHXN6J12WHXWMM0F9MC9CAPR339ET9VKGXA8S274TVHZXWC7JMKK5JSSTT3W1FK21G" + }, + { + "required_minimum_age":17, + "calculated_age_group":5, + "attestation":"FHV3JY9AX31D4K6VH6639Q5SGKJW4JEJM1CJKRW4VTWVR5P80AKABDAYTDZEXX9X5X1CZAGCW5C0V84JE2DBAJM50D6MXS39CZ33E30" + }, + { + "required_minimum_age":18, + "calculated_age_group":6, + "attestation":"P1NKNJ2RD63DQZM7MGW2C340GVSPAM2Y0YZ9VHMBSXWX5022SVBGJT4CAA26TER40G8TZ7YE8RCDKPPJS7XWTY3ZYRCCXCS0ZA00218" + }, + { + "required_minimum_age":19, + "calculated_age_group":6, + "attestation":"83Q4APZ0DHZWBSRY9ZZBA5SGW7ZEC9WKTGT3NTH2HV8ZVE8F67HE4WE14DGF0DS8YT40EHPT9H9P3ABMVHQJF6JVWXAPZ7QJE2FNA00" + }, + { + "required_minimum_age":20, + "calculated_age_group":6, + "attestation":"JVRNBR783423S311E6X9WQGNYXBEK2TVDSF5NJQFBDEVFY3N6N9KNN7ZBQNH0SBD8ZSG1CP4QE4BHS5S44WSJP9V38FG9VRHMREQE08" + }, + { + "required_minimum_age":21, + "calculated_age_group":7, + "attestation":"AGP53972S0KR80JESVH0DVAR3WJ3Q2DAHQ2GSEPF17J459CCZE781BBTWN0S7MH10ZSBVMEB50AQAD9C6FSH9W59AG4GK1WQ729K43G" + } + ] + } + ] + } + ] + } +] diff --git a/src/util/url.c b/src/util/url.c index 199863448..bf59ba6ec 100644 --- a/src/util/url.c +++ b/src/util/url.c @@ -102,12 +102,6 @@ buffer_write_urlencode (struct GNUNET_Buffer *buf, } -/** - * URL-encode a string according to rfc3986. - * - * @param s string to encode - * @returns the urlencoded string, the caller must free it with #GNUNET_free() - */ char * TALER_urlencode (const char *s) { @@ -212,28 +206,12 @@ serialize_arguments (struct GNUNET_Buffer *buf, } -/** - * Make an absolute URL with query parameters. - * - * If a 'value' is given as NULL, both the key and the value are skipped. Note - * that a NULL value does not terminate the list, only a NULL key signals the - * end of the list of arguments. - * - * @param base_url absolute base URL to use - * @param path path of the url - * @param ... NULL-terminated key-value pairs (char *) for query parameters, - * the value will be url-encoded - * @returns the URL (must be freed with #GNUNET_free) or - * NULL if an error occurred. - */ char * TALER_url_join (const char *base_url, const char *path, ...) { struct GNUNET_Buffer buf = { 0 }; - va_list args; - size_t len; GNUNET_assert (NULL != base_url); GNUNET_assert (NULL != path); @@ -244,59 +222,49 @@ TALER_url_join (const char *base_url, "Empty base URL specified\n"); return NULL; } - if ('/' != base_url[strlen (base_url) - 1]) + if ('\0' != path[0]) { - /* Must be an actual base URL! */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Base URL `%s' does not end with '/', cannot join with `%s'\n", - base_url, - path); - return NULL; + if ('/' != base_url[strlen (base_url) - 1]) + { + /* Must be an actual base URL! */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Base URL `%s' does not end with '/', cannot join with `%s'\n", + base_url, + path); + return NULL; + } + if ('/' == path[0]) + { + /* The path must be relative. */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Path `%s' is not relative\n", + path); + return NULL; + } } - if ('/' == path[0]) + { - /* The path must be relative. */ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Path `%s' is not relative\n", - path); - return NULL; + va_list args; + size_t len; + + va_start (args, + path); + len = strlen (base_url) + strlen (path) + 1; + len += calculate_argument_length (args); + GNUNET_buffer_prealloc (&buf, + len); + GNUNET_buffer_write_str (&buf, + base_url); + GNUNET_buffer_write_str (&buf, + path); + serialize_arguments (&buf, + args); + va_end (args); } - - va_start (args, - path); - - len = strlen (base_url) + strlen (path) + 1; - len += calculate_argument_length (args); - - GNUNET_buffer_prealloc (&buf, - len); - GNUNET_buffer_write_str (&buf, - base_url); - GNUNET_buffer_write_str (&buf, - path); - serialize_arguments (&buf, - args); - va_end (args); - return GNUNET_buffer_reap_str (&buf); } -/** - * Make an absolute URL for the given parameters. - * - * If a 'value' is given as NULL, both the key and the value are skipped. Note - * that a NULL value does not terminate the list, only a NULL key signals the - * end of the list of arguments. - * - * @param proto protocol for the URL (typically https) - * @param host hostname for the URL - * @param prefix prefix for the URL - * @param path path for the URL - * @param args NULL-terminated key-value pairs (char *) for query parameters, - * the value will be url-encoded - * @returns the URL, must be freed with #GNUNET_free - */ char * TALER_url_absolute_raw_va (const char *proto, const char *host, @@ -329,21 +297,6 @@ TALER_url_absolute_raw_va (const char *proto, } -/** - * Make an absolute URL for the given parameters. - * - * If a 'value' is given as NULL, both the key and the value are skipped. Note - * that a NULL value does not terminate the list, only a NULL key signals the - * end of the list of arguments. - * - * @param proto protocol for the URL (typically https) - * @param host hostname for the URL - * @param prefix prefix for the URL - * @param path path for the URL - * @param ... NULL-terminated key-value pairs (char *) for query parameters, - * the value will be url-encoded - * @return the URL, must be freed with #GNUNET_free - */ char * TALER_url_absolute_raw (const char *proto, const char *host, @@ -372,7 +325,7 @@ TALER_url_valid_charset (const char *url) for (unsigned int i = 0; '\0' != url[i]; i++) { #define ALLOWED_CHARACTERS \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%" + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/:;&?-.,=_~%+#" if (NULL == strchr (ALLOWED_CHARACTERS, (int) url[i])) return false; @@ -382,4 +335,20 @@ TALER_url_valid_charset (const char *url) } +bool +TALER_is_web_url (const char *url) +{ + if ( (0 != strncasecmp (url, + "https://", + strlen ("https://"))) && + (0 != strncasecmp (url, + "http://", + strlen ("http://"))) ) + return false; + if (! TALER_url_valid_charset (url) ) + return false; + return true; +} + + /* end of url.c */ diff --git a/src/util/util.c b/src/util/util.c index 274dad3cd..da5727487 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014-2020 Taler Systems SA + Copyright (C) 2014-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 @@ -19,16 +19,20 @@ * @author Sree Harsha Totakura <sreeharsha@totakura.in> * @author Florian Dold * @author Benedikt Mueller + * @author Christian Grothoff */ #include "platform.h" #include "taler_util.h" +#include "taler_attributes.h" +#include <gnunet/gnunet_json_lib.h> +#include <unistr.h> const char * TALER_b2s (const void *buf, size_t buf_size) { - static GNUNET_THREAD_LOCAL char ret[9]; + static TALER_THREAD_LOCAL char ret[9]; struct GNUNET_HashCode hc; char *tmp; @@ -37,13 +41,415 @@ TALER_b2s (const void *buf, &hc); tmp = GNUNET_STRINGS_data_to_string_alloc (&hc, sizeof (hc)); - memcpy (ret, - tmp, - 8); + GNUNET_memcpy (ret, + tmp, + 8); GNUNET_free (tmp); ret[8] = '\0'; return ret; } +void +TALER_denom_fee_set_hton (struct TALER_DenomFeeSetNBOP *nbo, + const struct TALER_DenomFeeSet *fees) +{ + TALER_amount_hton (&nbo->withdraw, + &fees->withdraw); + TALER_amount_hton (&nbo->deposit, + &fees->deposit); + TALER_amount_hton (&nbo->refresh, + &fees->refresh); + TALER_amount_hton (&nbo->refund, + &fees->refund); +} + + +void +TALER_denom_fee_set_ntoh (struct TALER_DenomFeeSet *fees, + const struct TALER_DenomFeeSetNBOP *nbo) +{ + TALER_amount_ntoh (&fees->withdraw, + &nbo->withdraw); + TALER_amount_ntoh (&fees->deposit, + &nbo->deposit); + TALER_amount_ntoh (&fees->refresh, + &nbo->refresh); + TALER_amount_ntoh (&fees->refund, + &nbo->refund); +} + + +void +TALER_global_fee_set_hton (struct TALER_GlobalFeeSetNBOP *nbo, + const struct TALER_GlobalFeeSet *fees) +{ + TALER_amount_hton (&nbo->history, + &fees->history); + TALER_amount_hton (&nbo->account, + &fees->account); + TALER_amount_hton (&nbo->purse, + &fees->purse); +} + + +void +TALER_global_fee_set_ntoh (struct TALER_GlobalFeeSet *fees, + const struct TALER_GlobalFeeSetNBOP *nbo) +{ + TALER_amount_ntoh (&fees->history, + &nbo->history); + TALER_amount_ntoh (&fees->account, + &nbo->account); + TALER_amount_ntoh (&fees->purse, + &nbo->purse); +} + + +void +TALER_wire_fee_set_hton (struct TALER_WireFeeSetNBOP *nbo, + const struct TALER_WireFeeSet *fees) +{ + TALER_amount_hton (&nbo->wire, + &fees->wire); + TALER_amount_hton (&nbo->closing, + &fees->closing); +} + + +void +TALER_wire_fee_set_ntoh (struct TALER_WireFeeSet *fees, + const struct TALER_WireFeeSetNBOP *nbo) +{ + TALER_amount_ntoh (&fees->wire, + &nbo->wire); + TALER_amount_ntoh (&fees->closing, + &nbo->closing); +} + + +int +TALER_global_fee_set_cmp (const struct TALER_GlobalFeeSet *f1, + const struct TALER_GlobalFeeSet *f2) +{ + int ret; + + ret = TALER_amount_cmp (&f1->history, + &f2->history); + if (0 != ret) + return ret; + ret = TALER_amount_cmp (&f1->account, + &f2->account); + if (0 != ret) + return ret; + ret = TALER_amount_cmp (&f1->purse, + &f2->purse); + if (0 != ret) + return ret; + return 0; +} + + +int +TALER_wire_fee_set_cmp (const struct TALER_WireFeeSet *f1, + const struct TALER_WireFeeSet *f2) +{ + int ret; + + ret = TALER_amount_cmp (&f1->wire, + &f2->wire); + if (0 != ret) + return ret; + ret = TALER_amount_cmp (&f1->closing, + &f2->closing); + if (0 != ret) + return ret; + return 0; +} + + +enum GNUNET_GenericReturnValue +TALER_denom_fee_check_currency ( + const char *currency, + const struct TALER_DenomFeeSet *fees) +{ + if (GNUNET_YES != + TALER_amount_is_currency (&fees->withdraw, + currency)) + { + GNUNET_break (0); + return GNUNET_NO; + } + if (GNUNET_YES != + TALER_amount_is_currency (&fees->deposit, + currency)) + { + GNUNET_break (0); + return GNUNET_NO; + } + if (GNUNET_YES != + TALER_amount_is_currency (&fees->refresh, + currency)) + { + GNUNET_break (0); + return GNUNET_NO; + } + if (GNUNET_YES != + TALER_amount_is_currency (&fees->refund, + currency)) + { + GNUNET_break (0); + return GNUNET_NO; + } + return GNUNET_OK; +} + + +/** + * Dump character in the low range into @a buf + * following RFC 8785. + * + * @param[in,out] buf buffer to modify + * @param val value to dump + */ +static void +lowdump (struct GNUNET_Buffer *buf, + unsigned char val) +{ + char scratch[7]; + + switch (val) + { + case 0x8: + GNUNET_buffer_write (buf, + "\\b", + 2); + break; + case 0x9: + GNUNET_buffer_write (buf, + "\\t", + 2); + break; + case 0xA: + GNUNET_buffer_write (buf, + "\\n", + 2); + break; + case 0xC: + GNUNET_buffer_write (buf, + "\\f", + 2); + break; + case 0xD: + GNUNET_buffer_write (buf, + "\\r", + 2); + break; + default: + GNUNET_snprintf (scratch, + sizeof (scratch), + "\\u%04x", + (unsigned int) val); + GNUNET_buffer_write (buf, + scratch, + 6); + break; + } +} + + +size_t +TALER_rfc8785encode (char **inp) +{ + struct GNUNET_Buffer buf = { 0 }; + size_t left = strlen (*inp) + 1; + size_t olen; + char *in = *inp; + const char *pos = in; + + GNUNET_buffer_prealloc (&buf, + left + 40); + buf.warn_grow = 0; /* disable, + 40 is just a wild guess */ + while (1) + { + int mbl = u8_mblen ((unsigned char *) pos, + left); + unsigned char val; + + if (0 == mbl) + break; + val = (unsigned char) *pos; + if ( (1 == mbl) && + (val <= 0x1F) ) + { + /* Should not happen, as input is produced by + * JSON stringification */ + GNUNET_break (0); + lowdump (&buf, + val); + } + else if ( (1 == mbl) && ('\\' == *pos) ) + { + switch (*(pos + 1)) + { + case '\\': + mbl = 2; + GNUNET_buffer_write (&buf, + pos, + mbl); + break; + case 'u': + { + unsigned int num; + uint32_t n32; + unsigned char res[8]; + size_t rlen; + + GNUNET_assert ( (1 == + sscanf (pos + 2, + "%4x", + &num)) || + (1 == + sscanf (pos + 2, + "%4X", + &num)) ); + mbl = 6; + n32 = (uint32_t) num; + rlen = sizeof (res); + u32_to_u8 (&n32, + 1, + res, + &rlen); + if ( (1 == rlen) && + (res[0] <= 0x1F) ) + { + lowdump (&buf, + res[0]); + } + else + { + GNUNET_buffer_write (&buf, + (const char *) res, + rlen); + } + } + break; + default: + mbl = 2; + GNUNET_buffer_write (&buf, + pos, + mbl); + break; + } + } + else + { + GNUNET_buffer_write (&buf, + pos, + mbl); + } + left -= mbl; + pos += mbl; + } + + /* 0-terminate buffer */ + GNUNET_buffer_write (&buf, + "", + 1); + GNUNET_free (in); + *inp = GNUNET_buffer_reap (&buf, + &olen); + return olen; +} + + +/** + * Hash normalized @a j JSON object or array and + * store the result in @a hc. + * + * @param j JSON to hash + * @param[out] hc where to write the hash + */ +void +TALER_json_hash (const json_t *j, + struct GNUNET_HashCode *hc) +{ + char *cstr; + size_t clen; + + cstr = json_dumps (j, + JSON_COMPACT | JSON_SORT_KEYS); + GNUNET_assert (NULL != cstr); + clen = TALER_rfc8785encode (&cstr); + GNUNET_CRYPTO_hash (cstr, + clen, + hc); + GNUNET_free (cstr); +} + + +#ifdef __APPLE__ +char * +strchrnul (const char *s, + int c) +{ + char *value; + value = strchr (s, + c); + if (NULL == value) + value = &s[strlen (s)]; + return value; +} + + +#endif + + +void +TALER_CRYPTO_attributes_to_kyc_prox ( + const json_t *attr, + struct GNUNET_ShortHashCode *kyc_prox) +{ + const char *name = NULL; + const char *birthdate = NULL; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string (TALER_ATTRIBUTE_FULL_NAME, + &name), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string (TALER_ATTRIBUTE_BIRTHDATE, + &birthdate), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (attr, + spec, + NULL, NULL)) + { + GNUNET_break (0); + memset (kyc_prox, + 0, + sizeof (*kyc_prox)); + return; + } + GNUNET_assert (GNUNET_YES == + GNUNET_CRYPTO_kdf ( + kyc_prox, + sizeof (*kyc_prox), + name, + (NULL == name) + ? 0 + : strlen (name), + birthdate, + (NULL == birthdate) + ? 0 + : strlen (birthdate), + NULL, + 0)); +} + + /* end of util.c */ diff --git a/src/util/wallet_signatures.c b/src/util/wallet_signatures.c index 8700d4a80..0b6ab5432 100644 --- a/src/util/wallet_signatures.c +++ b/src/util/wallet_signatures.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2021 Taler Systems SA + Copyright (C) 2021-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 @@ -17,23 +17,123 @@ * @file wallet_signatures.c * @brief Utility functions for Taler wallet signatures * @author Christian Grothoff + * @author Özgür Kesim */ #include "platform.h" #include "taler_util.h" #include "taler_signatures.h" +#include <gnunet/gnunet_common.h> +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used to generate the signature on a request to deposit + * a coin into the account of a merchant. + */ +struct TALER_DepositRequestPS +{ + /** + * Purpose must be #TALER_SIGNATURE_WALLET_COIN_DEPOSIT. + * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the contract for which this deposit is made. + */ + struct TALER_PrivateContractHashP h_contract_terms GNUNET_PACKED; + + /** + * Hash over the age commitment that went into the coin. Maybe all zero, if + * age commitment isn't applicable to the denomination. + */ + struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + + /** + * Hash over optional policy extension attributes shared with the exchange. + */ + struct TALER_ExtensionPolicyHashP h_policy GNUNET_PACKED; + + /** + * Hash over the wiring information of the merchant. + */ + struct TALER_MerchantWireHashP h_wire GNUNET_PACKED; + + /** + * Hash over the denomination public key used to sign the coin. + */ + struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED; + + /** + * Time when this request was generated. Used, for example, to + * assess when (roughly) the income was achieved for tax purposes. + * Note that the Exchange will only check that the timestamp is not "too + * far" into the future (i.e. several days). The fact that the + * timestamp falls within the validity period of the coin's + * denomination key is irrelevant for the validity of the deposit + * request, as obviously the customer and merchant could conspire to + * set any timestamp. Also, the Exchange must accept very old deposit + * requests, as the merchant might have been unable to transmit the + * deposit request in a timely fashion (so back-dating is not + * prevented). + */ + struct GNUNET_TIME_TimestampNBO wallet_timestamp; + + /** + * How much time does the merchant have to issue a refund request? + * Zero if refunds are not allowed. After this time, the coin + * cannot be refunded. + */ + struct GNUNET_TIME_TimestampNBO refund_deadline; + + /** + * Amount to be deposited, including deposit fee charged by the + * exchange. This is the total amount that the coin's value at the exchange + * will be reduced by. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Depositing fee charged by the exchange. This must match the Exchange's + * denomination key's depositing fee. If the client puts in an + * invalid deposit fee (too high or too low) that does not match the + * Exchange's denomination key, the deposit operation is invalid and + * will be rejected by the exchange. The @e amount_with_fee minus the + * @e deposit_fee is the amount that will be transferred to the + * account identified by @e h_wire. + */ + struct TALER_AmountNBO deposit_fee; + + /** + * The Merchant's public key. Allows the merchant to later refund + * the transaction or to inquire about the wire transfer identifier. + */ + struct TALER_MerchantPublicKeyP merchant; + + /** + * Hash over a JSON containing data provided by the + * wallet to complete the contract upon payment. + */ + struct GNUNET_HashCode wallet_data_hash; + +}; + +GNUNET_NETWORK_STRUCT_END + void TALER_wallet_deposit_sign ( const struct TALER_Amount *amount, const struct TALER_Amount *deposit_fee, - const struct TALER_MerchantWireHash *h_wire, - const struct TALER_PrivateContractHash *h_contract_terms, - const struct TALER_ExtensionContractHash *h_extensions, - const struct TALER_DenominationHash *h_denom_pub, - struct GNUNET_TIME_Absolute wallet_timestamp, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct GNUNET_HashCode *wallet_data_hash, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_ExtensionPolicyHashP *h_policy, + const struct TALER_DenominationHashP *h_denom_pub, + const struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, - struct GNUNET_TIME_Absolute refund_deadline, + const struct GNUNET_TIME_Timestamp refund_deadline, const struct TALER_CoinSpendPrivateKeyP *coin_priv, struct TALER_CoinSpendSignatureP *coin_sig) { @@ -43,17 +143,17 @@ TALER_wallet_deposit_sign ( .h_contract_terms = *h_contract_terms, .h_wire = *h_wire, .h_denom_pub = *h_denom_pub, - .wallet_timestamp = GNUNET_TIME_absolute_hton (wallet_timestamp), - .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline), + .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp), + .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), .merchant = *merchant_pub }; - if (NULL != h_extensions) - dr.h_extensions = *h_extensions; - GNUNET_assert (GNUNET_OK == - GNUNET_TIME_round_abs (&wallet_timestamp)); - GNUNET_assert (GNUNET_OK == - GNUNET_TIME_round_abs (&refund_deadline)); + if (NULL != wallet_data_hash) + dr.wallet_data_hash = *wallet_data_hash; + if (NULL != h_age_commitment) + dr.h_age_commitment = *h_age_commitment; + if (NULL != h_policy) + dr.h_policy = *h_policy; TALER_amount_hton (&dr.amount_with_fee, amount); TALER_amount_hton (&dr.deposit_fee, @@ -68,13 +168,15 @@ enum GNUNET_GenericReturnValue TALER_wallet_deposit_verify ( const struct TALER_Amount *amount, const struct TALER_Amount *deposit_fee, - const struct TALER_MerchantWireHash *h_wire, - const struct TALER_PrivateContractHash *h_contract_terms, - const struct TALER_ExtensionContractHash *h_extensions, - const struct TALER_DenominationHash *h_denom_pub, - struct GNUNET_TIME_Absolute wallet_timestamp, + const struct TALER_MerchantWireHashP *h_wire, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct GNUNET_HashCode *wallet_data_hash, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_ExtensionPolicyHashP *h_policy, + const struct TALER_DenominationHashP *h_denom_pub, + struct GNUNET_TIME_Timestamp wallet_timestamp, const struct TALER_MerchantPublicKeyP *merchant_pub, - struct GNUNET_TIME_Absolute refund_deadline, + struct GNUNET_TIME_Timestamp refund_deadline, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig) { @@ -84,13 +186,17 @@ TALER_wallet_deposit_verify ( .h_contract_terms = *h_contract_terms, .h_wire = *h_wire, .h_denom_pub = *h_denom_pub, - .wallet_timestamp = GNUNET_TIME_absolute_hton (wallet_timestamp), - .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline), - .merchant = *merchant_pub + .wallet_timestamp = GNUNET_TIME_timestamp_hton (wallet_timestamp), + .refund_deadline = GNUNET_TIME_timestamp_hton (refund_deadline), + .merchant = *merchant_pub, }; - if (NULL != h_extensions) - dr.h_extensions = *h_extensions; + if (NULL != wallet_data_hash) + dr.wallet_data_hash = *wallet_data_hash; + if (NULL != h_age_commitment) + dr.h_age_commitment = *h_age_commitment; + if (NULL != h_policy) + dr.h_policy = *h_policy; TALER_amount_hton (&dr.amount_with_fee, amount); TALER_amount_hton (&dr.deposit_fee, @@ -108,11 +214,48 @@ TALER_wallet_deposit_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used for to allow the wallet to authenticate + * link data provided by the exchange. + */ +struct TALER_LinkDataPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_COIN_LINK. + * Used with an EdDSA signature of a `struct TALER_CoinPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the denomination public key of the new coin. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * Transfer public key (for which the private key was not revealed) + */ + struct TALER_TransferPublicKeyP transfer_pub; + + /** + * Hash of the age commitment, if applicable. Can be all zero + */ + struct TALER_AgeCommitmentHash h_age_commitment; + + /** + * Hash of the blinded new coin. + */ + struct TALER_BlindedCoinHashP coin_envelope_hash; +}; + +GNUNET_NETWORK_STRUCT_END + void -TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub, +TALER_wallet_link_sign (const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_TransferPublicKeyP *transfer_pub, - const void *coin_ev, - size_t coin_ev_size, + const struct TALER_BlindedCoinHashP *bch, const struct TALER_CoinSpendPrivateKeyP *old_coin_priv, struct TALER_CoinSpendSignatureP *coin_sig) { @@ -120,12 +263,10 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub, .purpose.size = htonl (sizeof (ldp)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK), .h_denom_pub = *h_denom_pub, - .transfer_pub = *transfer_pub + .transfer_pub = *transfer_pub, + .coin_envelope_hash = *bch }; - GNUNET_CRYPTO_hash (coin_ev, - coin_ev_size, - &ldp.coin_envelope_hash.hash); GNUNET_CRYPTO_eddsa_sign (&old_coin_priv->eddsa_priv, &ldp, &coin_sig->eddsa_signature); @@ -134,10 +275,9 @@ TALER_wallet_link_sign (const struct TALER_DenominationHash *h_denom_pub, enum GNUNET_GenericReturnValue TALER_wallet_link_verify ( - const struct TALER_DenominationHash *h_denom_pub, + const struct TALER_DenominationHashP *h_denom_pub, const struct TALER_TransferPublicKeyP *transfer_pub, - const void *coin_ev, - size_t coin_ev_size, + const struct TALER_BlindedCoinHashP *h_coin_ev, const struct TALER_CoinSpendPublicKeyP *old_coin_pub, const struct TALER_CoinSpendSignatureP *coin_sig) { @@ -145,12 +285,10 @@ TALER_wallet_link_verify ( .purpose.size = htonl (sizeof (ldp)), .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_LINK), .h_denom_pub = *h_denom_pub, - .transfer_pub = *transfer_pub + .transfer_pub = *transfer_pub, + .coin_envelope_hash = *h_coin_ev, }; - GNUNET_CRYPTO_hash (coin_ev, - coin_ev_size, - &ldp.coin_envelope_hash.hash); return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_LINK, &ldp, @@ -159,4 +297,1534 @@ TALER_wallet_link_verify ( } +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Signed data to request that a coin should be refunded as part of + * the "emergency" /recoup protocol. The refund will go back to the bank + * account that created the reserve. + */ +struct TALER_RecoupRequestPS +{ + /** + * Purpose is #TALER_SIGNATURE_WALLET_COIN_RECOUP + * or #TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash of the (revoked) denomination public key of the coin. + */ + struct TALER_DenominationHashP h_denom_pub; + + /** + * Blinding factor that was used to withdraw the coin. + */ + union GNUNET_CRYPTO_BlindingSecretP coin_blind; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum GNUNET_GenericReturnValue +TALER_wallet_recoup_verify ( + const struct TALER_DenominationHashP *h_denom_pub, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RecoupRequestPS pr = { + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), + .purpose.size = htonl (sizeof (pr)), + .h_denom_pub = *h_denom_pub, + .coin_blind = *coin_bks + }; + + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP, + &pr, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +void +TALER_wallet_recoup_sign ( + const struct TALER_DenominationHashP *h_denom_pub, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RecoupRequestPS pr = { + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP), + .purpose.size = htonl (sizeof (pr)), + .h_denom_pub = *h_denom_pub, + .coin_blind = *coin_bks + }; + + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, + &pr, + &coin_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_recoup_refresh_verify ( + const struct TALER_DenominationHashP *h_denom_pub, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RecoupRequestPS pr = { + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH), + .purpose.size = htonl (sizeof (pr)), + .h_denom_pub = *h_denom_pub, + .coin_blind = *coin_bks + }; + + return GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH, + &pr, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +void +TALER_wallet_recoup_refresh_sign ( + const struct TALER_DenominationHashP *h_denom_pub, + const union GNUNET_CRYPTO_BlindingSecretP *coin_bks, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RecoupRequestPS pr = { + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_RECOUP_REFRESH), + .purpose.size = htonl (sizeof (struct TALER_RecoupRequestPS)), + .h_denom_pub = *h_denom_pub, + .coin_blind = *coin_bks + }; + + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, + &pr, + &coin_sig->eddsa_signature); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Message signed by a coin to indicate that the coin should be + * melted. + */ +struct TALER_RefreshMeltCoinAffirmationPS +{ + /** + * Purpose is #TALER_SIGNATURE_WALLET_COIN_MELT. + * Used for an EdDSA signature with the `struct TALER_CoinSpendPublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Which melt commitment is made by the wallet. + */ + struct TALER_RefreshCommitmentP rc GNUNET_PACKED; + + /** + * Hash over the denomination public key used to sign the coin. + */ + struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED; + + /** + * If age commitment was provided during the withdrawal of the coin, this is + * the hash of the age commitment vector. It must be all zeroes if no age + * commitment was provided. + */ + struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + + /** + * How much of the value of the coin should be melted? This amount + * includes the fees, so the final amount contributed to the melt is + * this value minus the fee for melting the coin. We include the + * fee in what is being signed so that we can verify a reserve's + * remaining total balance without needing to access the respective + * denomination key information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Melting fee charged by the exchange. This must match the Exchange's + * denomination key's melting fee. If the client puts in an invalid + * melting fee (too high or too low) that does not match the Exchange's + * denomination key, the melting operation is invalid and will be + * rejected by the exchange. The @e amount_with_fee minus the @e + * melt_fee is the amount that will be credited to the melting + * session. + */ + struct TALER_AmountNBO melt_fee; +}; + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_melt_sign ( + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *melt_fee, + const struct TALER_RefreshCommitmentP *rc, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RefreshMeltCoinAffirmationPS melt = { + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT), + .purpose.size = htonl (sizeof (melt)), + .rc = *rc, + .h_denom_pub = *h_denom_pub, + .h_age_commitment = {{{0}}}, + }; + + if (NULL != h_age_commitment) + melt.h_age_commitment = *h_age_commitment; + + + TALER_amount_hton (&melt.amount_with_fee, + amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + melt_fee); + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, + &melt, + &coin_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_melt_verify ( + const struct TALER_Amount *amount_with_fee, + const struct TALER_Amount *melt_fee, + const struct TALER_RefreshCommitmentP *rc, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_RefreshMeltCoinAffirmationPS melt = { + .purpose.size = htonl (sizeof (melt)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT), + .rc = *rc, + .h_denom_pub = *h_denom_pub, + .h_age_commitment = {{{0}}}, + }; + + if (NULL != h_age_commitment) + melt.h_age_commitment = *h_age_commitment; + + TALER_amount_hton (&melt.amount_with_fee, + amount_with_fee); + TALER_amount_hton (&melt.melt_fee, + melt_fee); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_COIN_MELT, + &melt, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * @brief Format used for to generate the signature on a request to withdraw + * coins from a reserve. + */ +struct TALER_WithdrawRequestPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW. + * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Value of the coin being exchanged (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Hash of the denomination public key for the coin that is withdrawn. + */ + struct TALER_DenominationHashP h_denomination_pub GNUNET_PACKED; + + /** + * Hash of the (blinded) message to be signed by the Exchange. + */ + struct TALER_BlindedCoinHashP h_coin_envelope GNUNET_PACKED; +}; + + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_withdraw_sign ( + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_BlindedCoinHashP *bch, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_WithdrawRequestPS req = { + .purpose.size = htonl (sizeof (req)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW), + .h_denomination_pub = *h_denom_pub, + .h_coin_envelope = *bch + }; + + TALER_amount_hton (&req.amount_with_fee, + amount_with_fee); + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req, + &reserve_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_withdraw_verify ( + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_Amount *amount_with_fee, + const struct TALER_BlindedCoinHashP *bch, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_WithdrawRequestPS wsrd = { + .purpose.size = htonl (sizeof (wsrd)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW), + .h_denomination_pub = *h_denom_pub, + .h_coin_envelope = *bch + }; + + TALER_amount_hton (&wsrd.amount_with_fee, + amount_with_fee); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW, + &wsrd, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * @brief Format used for to generate the signature on a request to + * age-withdraw from a reserve. + */ +struct TALER_AgeWithdrawRequestPS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW. + * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * The reserve's public key + */ + struct TALER_ReservePublicKeyP reserve_pub; + + /** + * Value of the coin being exchanged (matching the denomination key) + * plus the transaction fee. We include this in what is being + * signed so that we can verify a reserve's remaining total balance + * without needing to access the respective denomination key + * information each time. + */ + struct TALER_AmountNBO amount_with_fee; + + /** + * Running SHA512 hash of the commitment of n*kappa coins + */ + struct TALER_AgeWithdrawCommitmentHashP h_commitment; + + /** + * The mask that defines the age groups. MUST be the same for all denominations. + */ + struct TALER_AgeMask mask; + + /** + * Maximum age group that the coins are going to be restricted to. + */ + uint8_t max_age_group; +}; + + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_age_withdraw_sign ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + const struct TALER_AgeMask *mask, + uint8_t max_age, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AgeWithdrawRequestPS req = { + .purpose.size = htonl (sizeof (req)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW), + .h_commitment = *h_commitment, + .mask = *mask, + .max_age_group = TALER_get_age_group (mask, max_age) + }; + + GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, + &req.reserve_pub.eddsa_pub); + TALER_amount_hton (&req.amount_with_fee, + amount_with_fee); + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &req, + &reserve_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_age_withdraw_verify ( + const struct TALER_AgeWithdrawCommitmentHashP *h_commitment, + const struct TALER_Amount *amount_with_fee, + const struct TALER_AgeMask *mask, + uint8_t max_age, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AgeWithdrawRequestPS awsrd = { + .purpose.size = htonl (sizeof (awsrd)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW), + .reserve_pub = *reserve_pub, + .h_commitment = *h_commitment, + .mask = *mask, + .max_age_group = TALER_get_age_group (mask, max_age) + }; + + TALER_amount_hton (&awsrd.amount_with_fee, + amount_with_fee); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW, + &awsrd, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * @brief Format used for to generate the signature on a request to withdraw + * coins from a reserve. + */ +struct TALER_AccountSetupRequestSignaturePS +{ + + /** + * Purpose must be #TALER_SIGNATURE_WALLET_ACCOUNT_SETUP. + * Used with an EdDSA signature of a `struct TALER_ReservePublicKeyP`. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Balance threshold the wallet is about to cross. + */ + struct TALER_AmountNBO threshold; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_account_setup_sign ( + const struct TALER_ReservePrivateKeyP *reserve_priv, + const struct TALER_Amount *balance_threshold, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AccountSetupRequestSignaturePS asap = { + .purpose.size = htonl (sizeof (asap)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP) + }; + + TALER_amount_hton (&asap.threshold, + balance_threshold); + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &asap, + &reserve_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_account_setup_verify ( + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_Amount *balance_threshold, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AccountSetupRequestSignaturePS asap = { + .purpose.size = htonl (sizeof (asap)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_SETUP) + }; + + TALER_amount_hton (&asap.threshold, + balance_threshold); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_ACCOUNT_SETUP, + &asap, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * Response by which a wallet requests a reserve history. + */ +struct TALER_ReserveHistoryRequestPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_HISTORY + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Which entries to exclude. Only return above this offset. + */ + uint64_t start_off; + +}; + +GNUNET_NETWORK_STRUCT_END + + +enum GNUNET_GenericReturnValue +TALER_wallet_reserve_history_verify ( + uint64_t start_off, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveHistoryRequestPS rhr = { + .purpose.size = htonl (sizeof (rhr)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY), + .start_off = GNUNET_htonll (start_off) + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_RESERVE_HISTORY, + &rhr, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +void +TALER_wallet_reserve_history_sign ( + uint64_t start_off, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveHistoryRequestPS rhr = { + .purpose.size = htonl (sizeof (rhr)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_HISTORY), + .start_off = GNUNET_htonll (start_off) + }; + + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &rhr, + &reserve_sig->eddsa_signature); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Response by which a wallet requests a coin history. + */ +struct TALER_CoinHistoryRequestPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_COIN_HISTORY + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Which entries to exclude. Only return above this offset. + */ + uint64_t start_off; + +}; + +GNUNET_NETWORK_STRUCT_END + +enum GNUNET_GenericReturnValue +TALER_wallet_coin_history_verify ( + uint64_t start_off, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_CoinHistoryRequestPS rsr = { + .purpose.size = htonl (sizeof (rsr)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY), + .start_off = GNUNET_htonll (start_off) + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_COIN_HISTORY, + &rsr, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +void +TALER_wallet_coin_history_sign ( + uint64_t start_off, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_CoinHistoryRequestPS rsr = { + .purpose.size = htonl (sizeof (rsr)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_HISTORY), + .start_off = GNUNET_htonll (start_off) + }; + + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, + &rsr, + &coin_sig->eddsa_signature); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed to create a purse (without reserve). + */ +struct TALER_PurseCreatePS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_PURSE_CREATE + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time when the purse will expire if still unmerged or unpaid. + */ + struct GNUNET_TIME_TimestampNBO purse_expiration; + + /** + * Total amount (with fees) to be put into the purse. + */ + struct TALER_AmountNBO purse_amount; + + /** + * Contract this purse pays for. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Public key identifying the merge capability. + */ + struct TALER_PurseMergePublicKeyP merge_pub; + + /** + * Minimum age required for payments into this purse. + */ + uint32_t min_age GNUNET_PACKED; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_purse_create_sign ( + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PurseMergePublicKeyP *merge_pub, + uint32_t min_age, + const struct TALER_Amount *amount, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseCreatePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE), + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .h_contract_terms = *h_contract_terms, + .merge_pub = *merge_pub, + .min_age = htonl (min_age) + }; + + TALER_amount_hton (&pm.purse_amount, + amount); + GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv, + &pm, + &purse_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_purse_create_verify ( + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_PurseMergePublicKeyP *merge_pub, + uint32_t min_age, + const struct TALER_Amount *amount, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseCreatePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_CREATE), + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .h_contract_terms = *h_contract_terms, + .merge_pub = *merge_pub, + .min_age = htonl (min_age) + }; + + TALER_amount_hton (&pm.purse_amount, + amount); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_PURSE_CREATE, + &pm, + &purse_sig->eddsa_signature, + &purse_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed to delete a purse. + */ +struct TALER_PurseDeletePS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DELETE + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + +}; + + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_purse_delete_sign ( + const struct TALER_PurseContractPrivateKeyP *purse_priv, + struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseDeletePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE) + }; + + GNUNET_CRYPTO_eddsa_sign (&purse_priv->eddsa_priv, + &pm, + &purse_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_purse_delete_verify ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseDeletePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DELETE) + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_PURSE_DELETE, + &pm, + &purse_sig->eddsa_signature, + &purse_pub->eddsa_pub); +} + + +void +TALER_wallet_purse_status_sign ( + const struct TALER_PurseContractPrivateKeyP *purse_priv, + struct TALER_PurseContractSignatureP *purse_sig) +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose = { + .size = htonl (sizeof (purpose)), + .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS) + }; + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv, + &purpose, + &purse_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_purse_status_verify ( + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose = { + .size = htonl (sizeof (purpose)), + .purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_STATUS) + }; + + return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_STATUS, + &purpose, + &purse_sig->eddsa_signature, + &purse_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed to deposit a coin into a purse. + */ +struct TALER_PurseDepositPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_PURSE_DEPOSIT + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Amount (with deposit fee) to be deposited into the purse. + */ + struct TALER_AmountNBO coin_amount; + + /** + * Hash over the denomination public key used to sign the coin. + */ + struct TALER_DenominationHashP h_denom_pub GNUNET_PACKED; + + /** + * Hash over the age commitment that went into the coin. Maybe all zero, if + * age commitment isn't applicable to the denomination. + */ + struct TALER_AgeCommitmentHash h_age_commitment GNUNET_PACKED; + + /** + * Purse to deposit funds into. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Hash of the base URL of the exchange hosting the + * @e purse_pub. + */ + struct GNUNET_HashCode h_exchange_base_url GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_purse_deposit_sign ( + const char *exchange_base_url, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_Amount *amount, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_PurseDepositPS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT), + .purse_pub = *purse_pub, + .h_denom_pub = *h_denom_pub, + .h_age_commitment = *h_age_commitment + }; + + GNUNET_CRYPTO_hash (exchange_base_url, + strlen (exchange_base_url) + 1, + &pm.h_exchange_base_url); + TALER_amount_hton (&pm.coin_amount, + amount); + GNUNET_CRYPTO_eddsa_sign (&coin_priv->eddsa_priv, + &pm, + &coin_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_purse_deposit_verify ( + const char *exchange_base_url, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_Amount *amount, + const struct TALER_DenominationHashP *h_denom_pub, + const struct TALER_AgeCommitmentHash *h_age_commitment, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_PurseDepositPS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_DEPOSIT), + .purse_pub = *purse_pub, + .h_denom_pub = *h_denom_pub, + .h_age_commitment = *h_age_commitment + }; + + GNUNET_CRYPTO_hash (exchange_base_url, + strlen (exchange_base_url) + 1, + &pm.h_exchange_base_url); + TALER_amount_hton (&pm.coin_amount, + amount); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_PURSE_DEPOSIT, + &pm, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed to merge a purse into a reserve. + */ +struct TALER_PurseMergePS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_PURSE_MERGE + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time when the purse is merged into the reserve. + */ + struct GNUNET_TIME_TimestampNBO merge_timestamp; + + /** + * Which purse is being merged? + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Which reserve should the purse be merged with. + * Hash of the reserve's payto:// URI. + */ + struct TALER_PaytoHashP h_payto; + +}; + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_purse_merge_sign ( + const char *reserve_uri, + struct GNUNET_TIME_Timestamp merge_timestamp, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePrivateKeyP *merge_priv, + struct TALER_PurseMergeSignatureP *merge_sig) +{ + struct TALER_PurseMergePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .purse_pub = *purse_pub + }; + + GNUNET_assert (0 == + strncasecmp (reserve_uri, + "payto://taler-reserve", + strlen ("payto://taler-reserve"))); + TALER_payto_hash (reserve_uri, + &pm.h_payto); + GNUNET_CRYPTO_eddsa_sign (&merge_priv->eddsa_priv, + &pm, + &merge_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_purse_merge_verify ( + const char *reserve_uri, + struct GNUNET_TIME_Timestamp merge_timestamp, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseMergePublicKeyP *merge_pub, + const struct TALER_PurseMergeSignatureP *merge_sig) +{ + struct TALER_PurseMergePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_MERGE), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .purse_pub = *purse_pub + }; + + if (0 != + strncasecmp (reserve_uri, + "payto://taler-reserve", + strlen ("payto://taler-reserve"))) + { + GNUNET_break (0); + return GNUNET_NO; + } + TALER_payto_hash (reserve_uri, + &pm.h_payto); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_PURSE_MERGE, + &pm, + &merge_sig->eddsa_signature, + &merge_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by account to merge a purse into a reserve. + */ +struct TALER_AccountMergePS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_ACCOUNT_MERGE + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Time when the purse will expire if still unmerged or unpaid. + */ + struct GNUNET_TIME_TimestampNBO purse_expiration; + + /** + * Total amount (with fees) to be put into the purse. + */ + struct TALER_AmountNBO purse_amount; + + /** + * Purse creation fee to be paid by the reserve for + * this operation. + */ + struct TALER_AmountNBO purse_fee; + + /** + * Contract this purse pays for. + */ + struct TALER_PrivateContractHashP h_contract_terms; + + /** + * Purse to merge. + */ + struct TALER_PurseContractPublicKeyP purse_pub; + + /** + * Time when the purse is merged into the reserve. + */ + struct GNUNET_TIME_TimestampNBO merge_timestamp; + + /** + * Minimum age required for payments into this purse, + * in NBO. + */ + uint32_t min_age GNUNET_PACKED; + + /** + * Flags for the operation, in NBO. See + * `enum TALER_WalletAccountMergeFlags`. + */ + uint32_t flags GNUNET_PACKED; +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_account_merge_sign ( + struct GNUNET_TIME_Timestamp merge_timestamp, + const struct TALER_PurseContractPublicKeyP *purse_pub, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_Amount *amount, + const struct TALER_Amount *purse_fee, + uint32_t min_age, + enum TALER_WalletAccountMergeFlags flags, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AccountMergePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .purse_pub = *purse_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .h_contract_terms = *h_contract_terms, + .min_age = htonl (min_age), + .flags = htonl ((uint32_t) flags) + }; + + TALER_amount_hton (&pm.purse_amount, + amount); + TALER_amount_hton (&pm.purse_fee, + purse_fee); + GNUNET_CRYPTO_eddsa_sign (&reserve_priv->eddsa_priv, + &pm, + &reserve_sig->eddsa_signature); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_account_merge_verify ( + struct GNUNET_TIME_Timestamp merge_timestamp, + const struct TALER_PurseContractPublicKeyP *purse_pub, + struct GNUNET_TIME_Timestamp purse_expiration, + const struct TALER_PrivateContractHashP *h_contract_terms, + const struct TALER_Amount *amount, + const struct TALER_Amount *purse_fee, + uint32_t min_age, + enum TALER_WalletAccountMergeFlags flags, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_AccountMergePS pm = { + .purpose.size = htonl (sizeof (pm)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_ACCOUNT_MERGE), + .merge_timestamp = GNUNET_TIME_timestamp_hton (merge_timestamp), + .purse_pub = *purse_pub, + .purse_expiration = GNUNET_TIME_timestamp_hton (purse_expiration), + .h_contract_terms = *h_contract_terms, + .min_age = htonl (min_age), + .flags = htonl ((uint32_t) flags) + }; + + TALER_amount_hton (&pm.purse_amount, + amount); + TALER_amount_hton (&pm.purse_fee, + purse_fee); + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_WALLET_ACCOUNT_MERGE, + &pm, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by reserve key. + */ +struct TALER_ReserveOpenPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Amount to be paid from the reserve balance to open + * the reserve. + */ + struct TALER_AmountNBO reserve_payment; + + /** + * When was the request created. + */ + struct GNUNET_TIME_TimestampNBO request_timestamp; + + /** + * For how long should the reserve be kept open. + * (Determines amount to be paid.) + */ + struct GNUNET_TIME_TimestampNBO reserve_expiration; + + /** + * How many open purses should be included with the + * open reserve? + * (Determines amount to be paid.) + */ + uint32_t purse_limit GNUNET_PACKED; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_reserve_open_sign ( + const struct TALER_Amount *reserve_payment, + struct GNUNET_TIME_Timestamp request_timestamp, + struct GNUNET_TIME_Timestamp reserve_expiration, + uint32_t purse_limit, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveOpenPS rop = { + .purpose.size = htonl (sizeof (rop)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp), + .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration), + .purse_limit = htonl (purse_limit) + }; + + TALER_amount_hton (&rop.reserve_payment, + reserve_payment); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv, + &rop.purpose, + &reserve_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_reserve_open_verify ( + const struct TALER_Amount *reserve_payment, + struct GNUNET_TIME_Timestamp request_timestamp, + struct GNUNET_TIME_Timestamp reserve_expiration, + uint32_t purse_limit, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveOpenPS rop = { + .purpose.size = htonl (sizeof (rop)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp), + .reserve_expiration = GNUNET_TIME_timestamp_hton (reserve_expiration), + .purse_limit = htonl (purse_limit) + }; + + TALER_amount_hton (&rop.reserve_payment, + reserve_payment); + return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_OPEN, + &rop.purpose, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by + */ +struct TALER_ReserveOpenDepositPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Which reserve's opening signature should be paid for? + */ + struct TALER_ReserveSignatureP reserve_sig; + + /** + * Specifies how much of the coin's value should be spent on opening this + * reserve. + */ + struct TALER_AmountNBO coin_contribution; +}; + +GNUNET_NETWORK_STRUCT_END + + +// FIXME-#7267: add h_age_commitment, h_denom_pub to have proof! +void +TALER_wallet_reserve_open_deposit_sign ( + const struct TALER_Amount *coin_contribution, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_CoinSpendPrivateKeyP *coin_priv, + struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_ReserveOpenDepositPS rod = { + .purpose.size = htonl (sizeof (rod)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), + .reserve_sig = *reserve_sig + }; + + TALER_amount_hton (&rod.coin_contribution, + coin_contribution); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&coin_priv->eddsa_priv, + &rod.purpose, + &coin_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_reserve_open_deposit_verify ( + const struct TALER_Amount *coin_contribution, + const struct TALER_ReserveSignatureP *reserve_sig, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + const struct TALER_CoinSpendSignatureP *coin_sig) +{ + struct TALER_ReserveOpenDepositPS rod = { + .purpose.size = htonl (sizeof (rod)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT), + .reserve_sig = *reserve_sig + }; + + TALER_amount_hton (&rod.coin_contribution, + coin_contribution); + return GNUNET_CRYPTO_eddsa_verify_ ( + TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT, + &rod.purpose, + &coin_sig->eddsa_signature, + &coin_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by reserve key. + */ +struct TALER_ReserveClosePS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_RESERVE_CLOSE + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When was the request created. + */ + struct GNUNET_TIME_TimestampNBO request_timestamp; + + /** + * Hash of the payto://-URI of the target account + * for the closure, or all zeros for the reserve + * origin account. + */ + struct TALER_PaytoHashP target_account_h_payto; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_reserve_close_sign ( + struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_PaytoHashP *h_payto, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveClosePS rcp = { + .purpose.size = htonl (sizeof (rcp)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp) + }; + + if (NULL != h_payto) + rcp.target_account_h_payto = *h_payto; + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv, + &rcp.purpose, + &reserve_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_reserve_close_verify ( + struct GNUNET_TIME_Timestamp request_timestamp, + const struct TALER_PaytoHashP *h_payto, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveClosePS rcp = { + .purpose.size = htonl (sizeof (rcp)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_CLOSE), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp) + }; + + if (NULL != h_payto) + rcp.target_account_h_payto = *h_payto; + return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_RESERVE_CLOSE, + &rcp.purpose, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by reserve private key. + */ +struct TALER_ReserveAttestRequestPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_ATTEST_REQUEST + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * When was the request created. + */ + struct GNUNET_TIME_TimestampNBO request_timestamp; + + /** + * Hash over the JSON array of requested attributes. + */ + struct GNUNET_HashCode h_details; + +}; + +GNUNET_NETWORK_STRUCT_END + + +void +TALER_wallet_reserve_attest_request_sign ( + struct GNUNET_TIME_Timestamp request_timestamp, + const json_t *details, + const struct TALER_ReservePrivateKeyP *reserve_priv, + struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveAttestRequestPS rcp = { + .purpose.size = htonl (sizeof (rcp)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp) + }; + + TALER_json_hash (details, + &rcp.h_details); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&reserve_priv->eddsa_priv, + &rcp.purpose, + &reserve_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_reserve_attest_request_verify ( + struct GNUNET_TIME_Timestamp request_timestamp, + const json_t *details, + const struct TALER_ReservePublicKeyP *reserve_pub, + const struct TALER_ReserveSignatureP *reserve_sig) +{ + struct TALER_ReserveAttestRequestPS rcp = { + .purpose.size = htonl (sizeof (rcp)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS), + .request_timestamp = GNUNET_TIME_timestamp_hton (request_timestamp) + }; + + TALER_json_hash (details, + &rcp.h_details); + return GNUNET_CRYPTO_eddsa_verify_ ( + TALER_SIGNATURE_WALLET_RESERVE_ATTEST_DETAILS, + &rcp.purpose, + &reserve_sig->eddsa_signature, + &reserve_pub->eddsa_pub); +} + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Message signed by purse to associate an encrypted contract. + */ +struct TALER_PurseContractPS +{ + + /** + * Purpose is #TALER_SIGNATURE_WALLET_PURSE_ECONTRACT + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Hash over the encrypted contract. + */ + struct GNUNET_HashCode h_econtract; + + /** + * Public key to decrypt the contract. + */ + struct TALER_ContractDiffiePublicP contract_pub; +}; + +GNUNET_NETWORK_STRUCT_END + +void +TALER_wallet_econtract_upload_sign ( + const void *econtract, + size_t econtract_size, + const struct TALER_ContractDiffiePublicP *contract_pub, + const struct TALER_PurseContractPrivateKeyP *purse_priv, + struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseContractPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT), + .contract_pub = *contract_pub + }; + + GNUNET_CRYPTO_hash (econtract, + econtract_size, + &pc.h_econtract); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign_ (&purse_priv->eddsa_priv, + &pc.purpose, + &purse_sig->eddsa_signature)); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_econtract_upload_verify2 ( + const struct GNUNET_HashCode *h_econtract, + const struct TALER_ContractDiffiePublicP *contract_pub, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct TALER_PurseContractPS pc = { + .purpose.size = htonl (sizeof (pc)), + .purpose.purpose = htonl (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT), + .contract_pub = *contract_pub, + .h_econtract = *h_econtract + }; + + return GNUNET_CRYPTO_eddsa_verify_ (TALER_SIGNATURE_WALLET_PURSE_ECONTRACT, + &pc.purpose, + &purse_sig->eddsa_signature, + &purse_pub->eddsa_pub); +} + + +enum GNUNET_GenericReturnValue +TALER_wallet_econtract_upload_verify ( + const void *econtract, + size_t econtract_size, + const struct TALER_ContractDiffiePublicP *contract_pub, + const struct TALER_PurseContractPublicKeyP *purse_pub, + const struct TALER_PurseContractSignatureP *purse_sig) +{ + struct GNUNET_HashCode h_econtract; + + GNUNET_CRYPTO_hash (econtract, + econtract_size, + &h_econtract); + return TALER_wallet_econtract_upload_verify2 (&h_econtract, + contract_pub, + purse_pub, + purse_sig); +} + + /* end of wallet_signatures.c */ |