taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

024-age-restriction.rst (31939B)


      1 DD 24: Anonymous Age Restriction Extension
      2 ##########################################
      3 
      4 :Design status: Accepted
      5 :Implementation status: Implemented
      6 :DD shepherd: TBD
      7 :Historical contributors: Özgür Kesim, Christian Grothoff, Florian Dold
      8 :First published: 2021-10-12
      9 :Last substantive change: 2025-12-16
     10 :Implementation evidence: exchange (2022-03-01, 2022-03-28), taler-typescript-core (2022-04-19)
     11 :Normative references: :doc:`../core/api-exchange`
     12 
     13 .. note::
     14 
     15    Age restriction remains implemented.  The generic pluggable extension
     16    mechanism from DD06 has been retired; current exchange API documentation is
     17    authoritative for how age-restricted denominations are advertised and used.
     18 
     19 Summary
     20 =======
     21 
     22 This document presents and discusses an extension to GNU Taler that provides
     23 anonymous age-restriction.
     24 
     25 Motivation
     26 ==========
     27 
     28 Merchants are legally obliged to perform age verification of customers when
     29 they buy certain goods and services.  Current mechanisms for age verification
     30 are either ID-based or require the usage of credit/debit cards.  In all cases
     31 sensitive private information is disclosed.
     32 
     33 We want to offer a better mechanism for age-restriction with GNU Taler that
     34 
     35 * ensures anonymity and unlinkability of purchases
     36 * can be set to particular age groups by parents/wardens at withdrawal
     37 * is bound to particular coins/tokens
     38 * can be verified by the merchant at purchase time
     39 * persists even after refresh
     40 
     41 The mechanism is presented as an 'extension' to GNU Taler, that is, as an
     42 optional feature that can be switched on by the exchange operator.
     43 
     44 Requirements
     45 ============
     46 
     47 * legal requirements for merchants must allow for this kind of mechanism
     48 
     49 
     50 Proposed Solution
     51 =================
     52 
     53 We propose an extension to GNU Taler for age-restriction that can be enabled by
     54 an Exchange¹).
     55 
     56 Once enabled, coins with age restrictions can be withdrawn by parents/warden
     57 who can choose to **commit** the coins to a certain maximum age out of a
     58 predefined list of age groups.
     59 
     60 The minors/wards receive those coins and can now **attest** a required minimum
     61 age (provided that age is less or equal to the committed age of the coins) to
     62 merchants, who can **verify** the minimum age.
     63 
     64 For the rest values (change) after a transaction, the minor/ward can
     65 **derive** new age-restricted coins.  The exchange can **compare** the equality
     66 of the age-restriction of the old coin with the new coin (in a zero-knowledge
     67 protocol, that gives the minor/ward a 1/κ chance to raise the minimum age for
     68 the new coin).
     69 
     70 The proposed solution maintains the guarantees of GNU Taler with respect to
     71 anonymity and unlinkability.  We have published a paper
     72 `Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
     73 with the details.
     74 
     75 ¹) Once the feature is enabled and the age groups are defined, the exchange has
     76 to stick to that decision until the support for age restriction is disabled.
     77 We might reconsider this design decision at some point.
     78 
     79 
     80 Main ideas and building blocks
     81 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
     82 
     83 The main ideas are as follows:
     84 
     85 #. The exchange defines and publishes M+1 different *age groups* of increasing
     86    order: :math:`0 < a_1 < \ldots < a_M` with :math:`a_i \in \mathbb{N}`.  The
     87    zeroth age group is :math:`\{0,\ldots,a_1-1\}`.
     88 
     89 #. An **unrestricted age commitment** is defined as a vector of length M of
     90    pairs of Edx25519_ public and private keys on Curve25519. In other words: one
     91    key pair for each age group after the zeroth: :math:`\bigl\langle (q_1,
     92    p_1), \ldots, (q_M, p_M) \bigr\rangle`.  Here, :math:`q_i` are the public keys
     93    (mnemonic: **q-mitments**), :math:`p_i` are the private keys.
     94 
     95 #. A **restricted age commitment** *to age group m* is derived from an
     96    unrestricted age commitment by removing all private keys for
     97    indices larger than m: :math:`\bigl\langle (q_1, p_1), \ldots, (q_m, p_m),
     98    \, (q_{m+1}, \perp), \ldots, (q_M, \perp )\bigr\rangle`.  F.e. if *none* of
     99    the private keys is provided, the age commitment would be restricted to the
    100    zeroth age group.
    101 
    102 #. The act of restricting an unrestricted age commitment is performed by the
    103    parent/ward.
    104 
    105 #. An *age commitment* (without prefix) is just the vector of public keys:
    106    :math:`\vec{Q} := \langle q_1, \ldots, q_M \rangle`.  Note that from
    107    just the age commitment one can not deduce if it originated from an
    108    unrestricted or restricted one (and what age).
    109 
    110 #. An *attestation of age group k* is essentially the signature to any message
    111    with the private key for slot k, if the corresponding private key is
    112    available in a restricted age commitment.  (Unrestricted age commitments can
    113    attest for any age group).
    114 
    115 #. An age commitment is *bound to a particular coin* by incorporating the
    116    SHA256 hash value of the age commitment (i.e. the M public keys) into the
    117    signature of the coin.  So instead of signing :math:`\text{FDH}_N(C_p)` with
    118    the RSA private key of a denomination with support for age restriction, we
    119    sign :math:`\text{FDH}_N(C_p, h_Q)`.  Here, :math:`C_p` is the EdDSA public
    120    key of a coin and :math:`h_Q` is the hash of the age commitment :math:`\vec{Q}`.
    121    **Note:** A coin with age restriction can only be validated when both, the
    122    public key of the coin itself **and** the hash of the age commitment, are
    123    present.  This needs to be supported in each subsystem: Exchange, Wallet and
    124    Merchant.
    125 
    126 
    127 The five-function design based on ``Commit()``, ``Attest()``, ``Verify()``,
    128 ``Derive()`` and ``Compare()`` is described in the paper referenced below.
    129 
    130 Changes in the Exchange API
    131 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    132 
    133 The necessary changes in the exchange involve
    134 
    135 * indication of support for age restriction as an extension
    136 * modification of the refresh protocol (both, commit and reveal phase)
    137 * modification of the deposit protocol
    138 
    139 
    140 Extension for age restriction
    141 -----------------------------
    142 
    143 .. note::
    144 
    145   This subsection records the original integration with DD06.  The generic
    146   extension registration mechanism has since been retired; the current
    147   ``/keys`` schema is authoritative for age-restriction advertisement.
    148 
    149 
    150 The exchange indicates support for age-restriction in response to ``/keys`` by
    151 registering the extension ``age_restriction`` with a value type
    152 ``ExtensionAgeRestriction``:
    153 
    154 .. ts:def:: ExtensionAgeRestriction
    155 
    156    interface ExtensionAgeRestriction {
    157       // The field ``critical`` is mandatory for an extension.
    158       // Age restriction is not required to be understood by an client, so
    159       // ``critical`` will be set to ``false``.
    160       critical: false;
    161 
    162       // The field ``version`` is mandatory for an extension.  It is of type
    163       // `LibtoolVersion`.
    164       version: "1";
    165 
    166       // Age restriction specific configuration
    167       config: ConfigAgeRestriction;
    168    }
    169 
    170 .. ts:def:: ConfigAgeRestriction
    171 
    172    interface ConfigAgeRestriction {
    173       // The age groups.  This field is mandatory and binding in the sense
    174       // that its value is taken into consideration when signing the
    175       // age restricted denominations in the `ExchangeKeysResponse`
    176       age_groups: AgeGroups;
    177    }
    178 
    179 Age Groups
    180 ~~~~~~~~~~
    181 
    182 Age groups are represented as a finite list of positive, increasing integers
    183 that mark the beginning of the *next* age group.  The value 0 is omitted but
    184 implicitly marks the beginning of the *zeroth* age group and the first number
    185 in the list marks the beginning of the *first* age group.  Age groups are
    186 encoded as a colon separated string of integer values.  They are referred to by
    187 their *slot*, i.e.  "age group 3" is the age group that starts with the 3.
    188 integer in the list.
    189 
    190 For example: the string "8:10:12:14:16:18:21" represents the age groups
    191 
    192 0. {0,1,2,3,4,5,6,7}
    193 #. {8,9}
    194 #. {10,11}
    195 #. {12,13}
    196 #. {14,15}
    197 #. {16,17}
    198 #. {18,19,20}
    199 #. {21, ⋯ }
    200 
    201 The field ``age_groups`` of type `AgeGroups` is mandatory and binding in the
    202 sense that its value is taken into consideration when signing the denominations
    203 in ``ExchangeKeysResponse.age_restricted_denoms``.
    204 
    205 .. ts:def:: AgeGroups
    206 
    207    // Representation of the age groups as colon separated edges: Increasing
    208    // from left to right, the values mark the beginning of an age group up
    209    // to, but not including the next value.  The initial age group starts at
    210    // 0 and is not listed.  Example: "8:10:12:14:16:18:21".
    211    type AgeGroups = string;
    212 
    213 
    214 Age restricted denominations
    215 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    216 
    217 If age-restriction is registered as extension ``age_restriction``, as described
    218 above, the root-object ``ExchangeKeysResponse`` in response to ``/keys`` MUST
    219 be extended by an additional field ``age_restricted_denoms``.  This is an
    220 *additional* list of denominations that must be used during the modified
    221 ``refresh`` and ``deposit`` operations (see below).
    222 
    223 The data structure for those denominations is the same as for the regular ones
    224 in ``ExchangeKeysResponse.denoms``.  **However**, the following differences
    225 apply for each denomination in the list:
    226 
    227 1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
    228    is taken over the public key of the denomination **and** the string in
    229    ``ExtensionAgeRestriction.age_groups`` from the corresponding extension
    230    object (see above).
    231 
    232 2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
    233    ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``.
    234 
    235 And similar to ``.denoms``, if the query parameter ``last_issue_date`` was
    236 provided by the client, the exchange will only return the keys that have
    237 changed since the given timestamp.
    238 
    239 
    240 .. ts:def:: ExchangeKeysResponse
    241 
    242    interface ExchangeKeysResponse {
    243    //...
    244 
    245    // List of denominations that support age-restriction with the age groups
    246    // given in age_groups.  This is only set **iff** the extension
    247    // ``age_restriction`` is registered under ``entensions`` with type
    248    // ``ExtensionAgeRestriction``.
    249    //
    250    // The data structure for each denomination is the same as for the
    251    // denominations in ExchangeKeysResponse.denoms.  **However**, the
    252    // following differences apply for each denomination in the list:
    253    //
    254    //  1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
    255    //     is taken over the public key of the denomination __and__ the
    256    //     string in ``ExtensionAgeRestriction.age_groups`` from the
    257    //     corresponding extension object.
    258    //
    259    //  2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
    260    //     ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``
    261    //
    262    // Similar as for ``.denoms``, if the query parameter ``last_issue_date``
    263    // was provided by the client, the exchange will only return the keys that
    264    // have changed since the given timestamp.
    265    age_restricted_denoms: DenomCommon[];
    266 
    267    //...
    268    }
    269 
    270 
    271 SQL schema
    272 -----------
    273 
    274 The exchange has to mark denominations with support for age restriction as such
    275 in the database.  Also, during the melting phase of the refresh operation, the
    276 exchange will have to persist the SHA256 hash of the age commitment of the
    277 original coin.
    278 
    279 The schema for the exchange is changed as follows:
    280 
    281 .. sourcecode:: sql
    282 
    283    -- Everything in one big transaction
    284    BEGIN;
    285    -- Check patch versioning is in place.
    286    SELECT _v.register_patch('exchange-TBD', NULL, NULL);
    287 
    288    -- Support for age restriction is marked per denomination.
    289    ALTER TABLE denominations
    290      ADD COLUMN age_restricted BOOLEAN NOT NULL DEFAULT (false);
    291    COMMENT ON COLUMN denominations.age_restriced
    292      IS 'true if this denomination can be used for age restriction';
    293 
    294    -- During the melting phase of the refresh, the wallet has to present the
    295    -- hash value of the age commitment (only for denominations with support
    296    -- for age restriction).
    297    ALTER TABLE refresh_commitments
    298      ADD COLUMN age_commitment_h BYTEA CHECK (LENGTH(age_commitment_h)=64);
    299    COMMENT ON COLUMN refresh_commitments.age_commitment_h
    300      IS 'SHA256 hash of the age commitment of the old coin, iff the corresponding
    301          denomimination has support for age restriction, NULL otherwise.';
    302    COMMIT;
    303 
    304 Note the constraint on ``refresh_commitments.age_commitment_h``:  It can be
    305 NULL, but only iff the corresponding denomination (indirectly referenced via
    306 table ``known_coins``) has ``.age_restricted`` set to true.  This constraint
    307 can not be expressed reliably with SQL.
    308 
    309 
    310 Protocol changes
    311 ----------------
    312 
    313 Withdraw
    314 ~~~~~~~~
    315 
    316 The withdraw protocol is affected in the following situations:
    317 
    318 - A wire transfer to the exchange (to fill a reserve) was marked by the
    319   originating bank as coming from a bank account of a minor, belonging to a of
    320   a specific age group, or by other means.
    321 - A KYC-process has been performed with the owner of a reserve and the user has
    322   been identified as being a minor.
    323 - A Peer-to-Peer transaction was performed between customers.  The receiving
    324   customer's KYC result tells the exchange that the customer belongs to a
    325   specific age group.
    326 
    327 In these cases, the wallet will have to perform a zero-knowledge protocol with
    328 exchange as part of the withdraw protocol, which we sketch here.  Let
    329 
    330 - :math:`\kappa` be the same cut-and-choose parameter for the refresh-protocol.
    331 - :math:`\Omega \in E` be a published, nothing-up-my-sleeve, constant
    332   group-element on the elliptic curve.
    333 - :math:`a \in \{1,\ldots,M\}` be the maximum age (group) for which the wallet
    334   has to prove its commitment.
    335 
    336 The values :math:`\kappa`, :math:`\Omega` and :math:`a` are known to the
    337 Exchange and the Wallet.  Then, Wallet and Exchange run the following protocol
    338 for the withdrawal of one coin:
    339 
    340 - *Wallet*
    341    1. creates planchets :math:`C_i` for :math:`i \in \{1,\ldots,\kappa\}` as candidates for *one* coin.
    342    #. creates age-commitments :math:`\vec{Q}^i` for :math:`i \in \{1,\ldots,\kappa\}` as follows:
    343 
    344       a) creates :math:`a`-many Edx25519-keypairs :math:`(p^i_j, q^i_j)`
    345          randomly for :math:`j \in \{1,\ldots,a\}` (with public keys :math:`q^i_j`),
    346       #) chooses randomly :math:`(M - a)`-many scalars :math:`s^i_j` for :math:`j \in \{a+1,\ldots,M\}`,
    347       #) calculates :math:`\omega^i_j = s^i_j*\Omega` for :math:`j \in \{a+1,\ldots,M \}`,
    348       #) sets :math:`\vec{Q}^i := (q^i_1,\ldots,q^i_a,\omega^i_{a+1},\ldots,\omega^i_M)`
    349 
    350    #. calculates :math:`f_i := \text{FDH}(C_i, H(\vec{Q}^i))` for :math:`i \in \{ 1,\ldots,\kappa \}`.
    351    #. chooses random blindings :math:`\beta_i(.)` for :math:`i \in \{1,\ldots,\kappa\}`.  The blinding functions depend on the cipher (RSA, CS).
    352    #. sends :math:`(\beta_1(f_1),\ldots,\beta_\kappa(f_\kappa))` to the Exchange
    353 
    354 - *Exchange*
    355    7. receives :math:`(b_1,\ldots,b_\kappa)`
    356    #. calculates :math:`F := \text{H}(b_1||\ldots||b_\kappa)`
    357    #. chooses randomly :math:`\gamma \in \{1,\ldots,\kappa\}` and
    358    #. signs :math:`r := b_\gamma` resulting in signature :math:`\sigma_r`
    359    #. stores :math:`F \mapsto (r, \sigma_r)`
    360    #. sends :math:`\gamma` to the Wallet.
    361 
    362 - *Wallet*
    363    10. receives :math:`\gamma`
    364    #. sends to the Exchange the tuple :math:`\left(F, \vec{\beta}, \vec{\vec{Q}}, \vec{\vec{S}}\right)` with
    365 
    366       - :math:`F := \text{H}(\beta_1(f_1)||\ldots||\beta_\kappa(f_\kappa))`
    367       - :math:`\vec{\beta} := (\beta_1,\ldots,\beta_{\gamma-1},\bot,\beta_{\gamma+1},\ldots,\beta_\kappa)`
    368       - :math:`\vec{\vec{Q}} := (\vec{Q}^1,\ldots,\vec{Q}^{\gamma-1},\bot,\vec{Q}^{\gamma+1},\ldots,\vec{Q}^\kappa)`
    369       - :math:`\vec{\vec{S}} := (\vec{S}^1,\ldots,\vec{S}^{\gamma-1},\bot,\vec{S}^{\gamma+1},\ldots,\vec{S}^\kappa)`
    370         with :math:`\vec{S}^i := (s^i_j)`
    371 
    372 - *Exchange*
    373    12. receives :math:`\left(F, (\beta_i), (\vec{Q}^i), (\vec{B}^i) \right)`
    374    #. retrieves :math:`(r, \sigma_r)` from :math:`F` or bails out if not present
    375    #. calculates :math:`b_i := \beta_i\left(\text{FDH}(\vec{Q}^i)\right)` for :math:`i \neq \gamma`
    376    #. compares :math:`F \overset{?}{=} \text{H}(b_1||\ldots||b_{\gamma - 1}||r||b_{\gamma+1}||\ldots||b_\kappa)` and bails out on inequality
    377    #. for each :math:`\vec{B}^i, i \neq \gamma`
    378 
    379       i. calculates :math:`\tilde{\omega}^i_j := b^i_j * \Omega` for :math:`j \in \{a+1,\ldots,M\}`
    380       #. compares each :math:`\tilde{\omega}^i_j` to :math:`q^i_j` from :math:`\vec{Q}^i = (q^i_1, \ldots, q^i_M)` and bails out on inequality
    381    #. sends (blinded) signature :math:`\sigma_r` to Wallet
    382 
    383 - *Wallet*
    384    18. receives :math:`\sigma_r`
    385    #. calculates (unblinded) signature :math:`\sigma_\gamma := \beta^{-1}_\gamma(\sigma_r)` for coin :math:`C_\gamma`.
    386 
    387 
    388 Note that the batch version of withdraw allows the withdrawal of *multiple*
    389 coins at once.  For that scenario the protocol sketched above is adapted to
    390 accommodate handling multiple coins at once -- thus multiplying the amount
    391 of data by the amount of coins in question--, but all with the same value of
    392 :math:`\gamma`.
    393 
    394 The *actual* implementation of the protocol above will have major optimizations
    395 to keep the bandwidth usage to a minimum and also ensure that a denomination in
    396 the commitment doesn't expire before the reveal.
    397 
    398 Instead of generating and sending the age commitment (array of public keys) and
    399 blindings for each coin, the wallet *MUST* derive the corresponding blindings
    400 and the age commitments from the coin's private key itself as follows:
    401 
    402 Let
    403 
    404 - :math:`s` be the master secret of the coin, from which the private key :math:`c_s`, blinding :math:`\beta` and nonce :math:`n` are derived as usual in the wallet core
    405 - :math:`m \in \{1,\ldots,M\}` be the maximum age (according to the reserve)
    406   that a wallet can commit to during the withdrawal.
    407 - :math:`P` be a published constant Edx25519-public-key to which the private
    408   key is not known to any client.
    409 
    410 For the age commitment, calculate:
    411 
    412 1. For age group :math:`a \in \{1,\ldots,m\}`, set
    413 
    414 .. math::
    415            s_a &:= \text{HDKF}(s, \text{"age-commitment"}, a) \\
    416            p_a &:= \text{Edx25519\_generate\_private}(s_a) \\
    417            q_a &:= \text{Edx25519\_public\_from\_private}(p_a)
    418 
    419 2. For age group :math:`a \in \{m,\ldots,M\}`, set
    420 
    421 .. math::
    422            f_a &:= \text{HDKF}(s, \text{"age-factor"}, a) \\
    423            q_a &:= \text{Edx25519\_derive\_public}(P, f_a).
    424 
    425 Then the vector :math:`\vec{q} = \{q_1,\ldots,q_M\}` is then the age commitment
    426 associated to the coin's private key :math:`c_s`.  For the non-disclosed coins,
    427 the wallet can use the vector :math:`(p_1,\ldots,p_m,\bot,\ldots,\bot)` of
    428 private keys for the attestation.
    429 
    430 Provided with the secret :math:`s`, the exchange can therefore calculate the
    431 private key :math:`c_s`, the blinding :math:`\beta`, the nonce :math:`n` (if
    432 needed) and the age commitment :math:`\vec{q}`, along with the coin's public
    433 key :math:`C_p` and use the value of
    434 
    435 .. math::
    436 
    437   \text{TALER\_CoinPubHashP}(C_p, \text{age\_commitment\_hash}(\vec{q}))
    438 
    439 during the verification of the original age-withdraw-commitment.
    440 
    441 For the withdrawal with age restriction, a sketch of the corresponding database
    442 schema in the exchange is given here:
    443 
    444 .. graphviz::
    445 
    446 	digraph deposit_policies {
    447 		rankdir = LR;
    448 		splines = true;
    449 		fontname="monospace"
    450 		node [
    451 			fontname="monospace"
    452 			shape=record
    453 		]
    454 
    455 		subgraph cluster_commitments {
    456 			label=<<B>age_withdraw</B>>
    457 			margin=20
    458 			commitments [
    459 			  label="age_withdraw_id\l|<hc>h_commitment\l|amount_with_fee_val\l|amount_with_fee_frac\l|noreveal_index\l|max_age\l|<res>reserve_pub\l|reserve_sig\l|<denom>[n] denominations_serials\l|[n] h_blind_evs\l|[n] denom_sigs\l"
    460 			]
    461 		}
    462 
    463 		commitments:res->reserves:id [ label="n:1"; fontname="monospace"];
    464 		commitments:denom -> denominations:id [ label="n:1"; fontname="monospace"] ;
    465 	}
    466 
    467 
    468 Refresh - melting phase
    469 ~~~~~~~~~~~~~~~~~~~~~~~
    470 
    471 During the melting phase of the refresh, the wallet has to present the hash
    472 value of the age commitment (for denominations with support for age
    473 restriction).  Therefore, in the ``/coins/$COIN_PUB/melt`` POST request, the
    474 ``MeltRequest`` object is extended with an optional field
    475 ``age_commitment_hash``:
    476 
    477 .. ts:def:: AgeMeltRequest
    478 
    479    interface AgeMeltRequest {
    480       ...
    481 
    482       // SHA256 hash of the age commitment of the coin, IFF the denomination
    483       // has age restriction support.  MUST be omitted otherwise.
    484       age_commitment_hash?: AgeCommitmentHash;
    485 
    486       ...
    487    }
    488 
    489 .. ts:def:: AgeCommitmentHash
    490 
    491    type AgeCommitmentHash = SHA256HashCode;
    492 
    493 The responses to the POST request remain the same.
    494 
    495 For normal denominations *without* support for age restriction, the calculation
    496 for the signature check is as before (borrowing notation from
    497 `Florian's thesis <https://taler.net/papers/thesis-dold-phd-2019.pdf>`_):
    498 
    499 .. math::
    500    \text{FDH}_N(C_p)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}\,N
    501 
    502 Here, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is its
    503 signature and :math:`\langle e, N \rangle` is the RSA public key of the
    504 denomination.
    505 
    506 For denominations *with* support for age restriction, the exchange takes the
    507 hash value ``age_commitment_hash`` (abbreviated as :math:`h_a`) into account
    508 when verifying the coin's signature:
    509 
    510 .. math::
    511    \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
    512 
    513 
    514 
    515 
    516 Refresh - reveal phase
    517 ~~~~~~~~~~~~~~~~~~~~~~
    518 
    519 During the reveal phase -- that is upon POST to ``/refreshes/$RCH/reveal`` --
    520 the client has to provide the original age commitment of the old coin (i.e. the
    521 vector of public keys), iff the corresponding denomination had support for age
    522 restriction.  The size of the vector is defined by the Exchange implicitly as
    523 the amount of age groups defined in the field ``.age_groups`` of the
    524 ``ExtensionAgeRestriction``.
    525 
    526 .. ts:def:: RevealRequest
    527 
    528    interface RevealRequest {
    529       ...
    530 
    531       // Iff the corresponding denomination has support for age restriction,
    532       // the client MUST provide the original age commitment, i.e. the vector
    533       // of public keys.
    534       // The size of the vector is defined by the Exchange implicitly as the
    535       // amount of age groups defined in the field ``.age_groups`` of the
    536       // ``ExtensionAgeRestriction``.
    537       old_age_commitment?: Edx25519PublicKey[];
    538 
    539 
    540       ...
    541    }
    542 
    543 
    544 The exchange can now check if the provided public keys ``.old_age_commitment``
    545 have the same SHA256 hash value when hashed in sequence as the
    546 ``age_commitment_hash`` of the original coin from the call to melt.
    547 
    548 The existing `cut&choose protocol during the reveal phase
    549 </core/api-exchange.html#post--refreshes-$RCH-reveal>`__ is extended to perform
    550 the following additional computation and checks:
    551 
    552 Using the κ-1 transfer secrets :math:`\tau_i` from the reveal request, the
    553 exchange derives κ-1 age commitments from the ``old_age_commitment`` by calling
    554 ``Edx25519_derive_public()`` on each `Edx25519PublicKey`, with :math:`\tau_i`
    555 as the seed, and then calculates the corresponding κ-1 hash values :math:`h_i`
    556 of those age commitments.
    557 
    558 It then calculates the κ-1 blinded hashes
    559 :math:`m_i = r^{e_i}\text{FDH}_N(C^{(i)}_p, h_i)` (using the notation from Florian's
    560 thesis) of the disclosed coins and together with the :math:`m_\gamma` of the
    561 undisclosed coin, calculates the hash
    562 :math:`h'_m = H(m_1,\cdots,m_\gamma,\cdots,m_\kappa)` which is then used in the
    563 final verification step of the cut&choose protocol.
    564 
    565 
    566 Deposit
    567 ~~~~~~~
    568 
    569 As always, the merchant has to provide the public key of a coin during a POST
    570 to ``/coins/$COIN_PUB/deposit``.  However, for coins with age restriction, the
    571 signature check requires the hash of the age commitment.  Therefore the request
    572 object ``DepositRequest`` is extended by an optional field
    573 ``age_commitment_hash`` which MUST be set (with the SHA256 hash of the age
    574 commitment), iff the corresponding denomination had support for age restriction
    575 enabled.  The merchant has received this value prior from the customer during
    576 purchase.
    577 
    578 .. ts:def:: DepositRequest
    579 
    580    interface DepositRequest {
    581    ...
    582 
    583    // Iff the corresponding denomination had support for age restriction
    584    // enabled, this field MUST contain the SHA256 value of the age commitment that
    585    // was provided during the purchase.
    586    age_commitment_hash?: AgeCommitmentHash;
    587 
    588    ...
    589    }
    590 
    591 Again, the exchange can now check the validity of the coin with age restriction
    592 by evaluating
    593 
    594 .. math::
    595    \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
    596 
    597 Also again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
    598 its signature, :math:`\langle e, N \rangle` is the RSA public key of the
    599 denomination and :math:`h_a` is the value from ``age_commitment_hash``.
    600 
    601 
    602 
    603 Changes in the Merchant API
    604 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
    605 
    606 
    607 Claiming the order
    608 ------------------
    609 
    610 If an order requires a minimum age, the merchant MUST express that required
    611 minimum age in response to order claim by the wallet, that is, a POST to
    612 ``[/instances/$INSTANCE]/orders/$ORDER_ID/claim``.
    613 
    614 The object ``ContractTerms`` is extended by an optional field
    615 ``minimum_age`` that can be any integer greater than 0.  In reality
    616 this value will not be smaller than, say, 8, and not larger than, say, 21.
    617 
    618 .. ts:def:: DD24ContractTerms
    619 
    620    interface DD24ContractTerms {
    621    ...
    622 
    623    // If the order requires a minimum age greater than 0, this field is set
    624    // to the integer value of that age.  In reality this value will not be
    625    // smaller than, say, 8, and not larger than, say, 21.
    626    minimum_age?: Integer;
    627 
    628    ...
    629    }
    630 
    631 By sending the contract term with the field ``minimum_age`` set to an
    632 a non-zero integer value, the merchant implicitly signals that it understands the
    633 extension ``age_restriction`` for age restriction from the exchange.
    634 
    635 
    636 Making the payment
    637 ------------------
    638 
    639 If the ``ContractTerms`` had a non-zero value in field
    640 ``minimum_age``, the wallet has to provide evidence of that minimum
    641 age by
    642 
    643 #. *either* using coins which are of denominations that had *no* age support
    644    enabled,
    645 
    646 #. *or* using coins which are of denominations that have support for age
    647    restriction enabled
    648 
    649    * and then  ―for each such coin― it has the right private key of the
    650      restricted age commitment to the age group into which the required minimum
    651      age falls (i.e.  a non-empty entry at the right index in vector of Edx25519
    652      keys, see above).
    653 
    654    * and signs the required minimum age with each coin's private key
    655      corresponding to the age group,
    656 
    657    * and sends ―for each coin― the complete age commitment and the signature to
    658      the merchant.
    659 
    660 The object ``CoinPaySig`` used within a ``PayRequest`` during a POST to
    661 ``[/instances/$INSTANCE]/orders/$ORDER_ID/pay`` is extended as follows:
    662 
    663 .. ts:def:: AgeCoinPaySig
    664 
    665    export interface AgeCoinPaySig extends CoinPaySig {
    666    ...
    667 
    668    // If a minimum age was required by the order and the wallet had coins that
    669    // are at least committed to the corresponding age group, this is the
    670    // signature of the minimum age as a string, using the private key to the
    671    // corresponding age group.
    672    minimum_age_sig?: Edx25519Signature;
    673 
    674    // If a minimum age was required by the order, this is age commitment bound
    675    // to the coin, i.e. the complete vector of Edx25519_ public keys, one for each
    676    // age group (as defined by the exchange).
    677    age_commitment?: Edx25519PublicKey[];
    678 
    679    }
    680 
    681 
    682 The merchant can now verify
    683 
    684 #. the validity of each (age restricted) coin by evaluating
    685 
    686    .. math:: \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
    687 
    688    Again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
    689    its signature, :math:`\langle e, N \rangle` is the RSA public key of the
    690    denomination and :math:`h_a` is the SHA256 hash value of the vector in
    691    ``age_commitment``.
    692 
    693 #. the minimum age requirement by checking the signature in ``minimum_age_sig``
    694    against the public key ``age_commitment[k]`` of the corresponding age group,
    695    say, ``k``.  (The minimum age must fall into the age group at index ``k`` as
    696    defined by the exchange).
    697 
    698 **Note**: This applies only to coins for denominations that have support for
    699 age restriction.  Denominations *without* support for age restriction *always*
    700 satisfy any minimum age requirement.
    701 
    702 
    703 
    704 Changes in the Wallet
    705 ^^^^^^^^^^^^^^^^^^^^^
    706 
    707 A wallet implementation SHOULD support denominations with age restriction.  In
    708 that case it SHOULD allow to select an age group as upper bound during
    709 withdraw.
    710 
    711 
    712 Alternatives
    713 ============
    714 
    715 * ID-based systems
    716 * credit/debit card based systems
    717 
    718 
    719 Drawbacks
    720 =========
    721 
    722 * age groups, once defined, are set permanently
    723 
    724 Also discuss:
    725 
    726 * storage overhead
    727 * computational overhead
    728 * bandwidth overhead
    729 * legal issues?
    730 
    731 Discussion / Q&A
    732 ================
    733 
    734 We had some very engaged discussions on the GNU Taler `mailing list <taler@gnu.org>`__:
    735 
    736 * `Money with capabilities <https://lists.gnu.org/archive/html/taler/2021-08/msg00005.html>`_
    737 
    738 * `On age-restriction (was: online games in China) <https://lists.gnu.org/archive/html/taler/2021-09/msg00006.html>`__
    739 
    740 * `Age-restriction is about coins, not currencies <https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`__
    741 
    742 * The published paper: `Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
    743 
    744 
    745 .. _Edx25519:
    746 
    747 Edx25519
    748 ========
    749 
    750 Edx25519 is a variant of EdDSA on curve25519 which allows for repeated
    751 derivation of private and public keys, independently.  It is implemented in
    752 `GNUNET with commit ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32.
    753 <https://git.gnunet.org/gnunet.git/commit/?id=ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32>`__
    754 
    755 The private keys in Edx25519 initially correspond to the data after expansion
    756 and clamping in EdDSA.  However, this correspondence is lost after deriving
    757 further keys from existing ones.  The public keys and signature verification
    758 are compatible with EdDSA.
    759 
    760 The scheme is as follows:
    761 
    762 ::
    763 
    764     /* Private keys in Edx25519 are pairs (a, b) of 32 byte each.
    765      * Initially they correspond to the result of the expansion
    766      * and clamping in EdDSA.
    767      */
    768 
    769     Edx25519_generate_private(seed) {
    770       /* EdDSA expand and clamp */
    771       dh := SHA-512(seed)
    772       a := dh[0..31]
    773       b := dh[32..64]
    774       a[0]  &= 0b11111000
    775       a[31] &= 0b01111111
    776       a[31] |= 0b01000000
    777 
    778       return (a, b)
    779     }
    780 
    781     Edx25519_public_from_private(private) {
    782       /* Public keys are the same as in EdDSA */
    783       (a, _) := private
    784       return [a] * G
    785     }
    786 
    787     Edx25519_blinding_factor(P, seed) {
    788       /* This is a helper function used in the derivation of
    789        * private/public keys from existing ones.  */
    790       h1 := HKDF_32(P, seed)
    791 
    792       /* Ensure that h == h % L */
    793       h := h1 % L
    794 
    795       /* Optionally: Make sure that we don't create weak keys. */
    796       P' := [h] * P
    797       if !( (h!=1) && (h!=0) && (P'!=E) ) {
    798         return Edx25519_blinding_factor(P, seed+1)
    799       }
    800 
    801       return h
    802     }
    803 
    804     Edx25519_derive_private(private, seed) {
    805       /* This is based on the definition in
    806        * GNUNET_CRYPTO_eddsa_private_key_derive.  But it accepts
    807        * and returns a private pair (a, b) and allows for iteration.
    808        */
    809       (a, b) := private
    810       P := Edx25519_public_key_from_private(private)
    811       h := Edx25519_blinding_factor(P, seed)
    812 
    813       /* Carefully calculate the new value for a */
    814       a1 := a / 8;
    815       a2 := (h  * a1) % L
    816       a' := (a2 *  8) % L
    817 
    818       /* Update b as well, binding it to h.
    819          This is an additional step compared to GNS. */
    820       b' := SHA256(b ∥ h)
    821 
    822       return (a', b')
    823     }
    824 
    825     Edx25519_derive_public(P, seed) {
    826       h := Edx25519_blinding_factor(P, seed)
    827       return [h]*P
    828     }
    829 
    830     Edx25519_sign(private, message) {
    831       /* As in Ed25519, except for the origin of b */
    832       (d, b) := private
    833       P := Edx25519_public_from_private(private)
    834       r := SHA-512(b ∥ message)
    835       R := [r] * G
    836       s := r + SHA-512(R ∥ P ∥ message) * d % L
    837 
    838       return (R,s)
    839     }
    840 
    841     Edx25519_verify(P, message, signature) {
    842       /* Identical to Ed25519 */
    843       (R, s) := signature
    844       return [s] * G == R + [SHA-512(R ∥ P ∥ message)] * P
    845     }