summaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/.gitignore5
-rw-r--r--src/util/Makefile.am67
-rw-r--r--src/util/age_restriction.c795
-rw-r--r--src/util/aml_signatures.c201
-rw-r--r--src/util/amount.c83
-rw-r--r--src/util/auditor_signatures.c151
-rw-r--r--src/util/bench_age_restriction.c208
-rw-r--r--src/util/config.c446
-rw-r--r--src/util/conversion.c405
-rw-r--r--src/util/crypto.c321
-rw-r--r--src/util/crypto_confirmation.c293
-rw-r--r--src/util/crypto_contract.c661
-rw-r--r--src/util/crypto_helper_cs.c1316
-rw-r--r--src/util/crypto_helper_esign.c27
-rw-r--r--src/util/crypto_helper_rsa.c395
-rw-r--r--src/util/crypto_wire.c86
-rw-r--r--src/util/currencies.conf89
-rw-r--r--src/util/denom.c634
-rwxr-xr-xsrc/util/do_bench_age_restriction8
-rw-r--r--src/util/exchange_signatures.c1861
-rw-r--r--src/util/extension_age_restriction.c181
-rw-r--r--src/util/iban.c6
-rw-r--r--src/util/merchant_signatures.c352
-rw-r--r--src/util/offline_signatures.c1085
-rw-r--r--src/util/paths.conf6
-rw-r--r--src/util/payto.c497
-rw-r--r--src/util/secmod_common.c50
-rw-r--r--src/util/secmod_common.h12
-rw-r--r--src/util/secmod_signatures.c141
-rw-r--r--src/util/taler-config.in4
-rw-r--r--src/util/taler-crypto-worker.c281
-rw-r--r--src/util/taler-exchange-secmod-cs.c2344
-rw-r--r--src/util/taler-exchange-secmod-cs.conf23
-rw-r--r--src/util/taler-exchange-secmod-cs.h319
-rw-r--r--src/util/taler-exchange-secmod-eddsa.c168
-rw-r--r--src/util/taler-exchange-secmod-eddsa.conf8
-rw-r--r--src/util/taler-exchange-secmod-eddsa.h2
-rw-r--r--src/util/taler-exchange-secmod-rsa.c876
-rw-r--r--src/util/taler-exchange-secmod-rsa.conf13
-rw-r--r--src/util/taler-exchange-secmod-rsa.h33
-rw-r--r--src/util/test_age_restriction.c442
-rw-r--r--src/util/test_amount.c17
-rw-r--r--src/util/test_conversion.c149
-rwxr-xr-xsrc/util/test_conversion.sh5
-rw-r--r--src/util/test_crypto.c361
-rw-r--r--src/util/test_helper_cs.c1177
-rw-r--r--src/util/test_helper_cs.conf11
-rw-r--r--src/util/test_helper_eddsa.c10
-rw-r--r--src/util/test_helper_eddsa.conf1
-rw-r--r--src/util/test_helper_rsa.c449
-rw-r--r--src/util/test_helper_rsa.conf3
-rw-r--r--src/util/test_payto.c64
-rw-r--r--src/util/tv_age_restriction.c271
-rw-r--r--src/util/tv_age_restriction.json9764
-rw-r--r--src/util/url.c135
-rw-r--r--src/util/util.c416
-rw-r--r--src/util/wallet_signatures.c1748
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'",
+ &section),
+ 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'",
+ &section),
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'",
+ &section),
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 */