summaryrefslogtreecommitdiff
path: root/design-documents/024-age-restriction.rst
diff options
context:
space:
mode:
Diffstat (limited to 'design-documents/024-age-restriction.rst')
-rw-r--r--design-documents/024-age-restriction.rst830
1 files changed, 830 insertions, 0 deletions
diff --git a/design-documents/024-age-restriction.rst b/design-documents/024-age-restriction.rst
new file mode 100644
index 00000000..0445aa6d
--- /dev/null
+++ b/design-documents/024-age-restriction.rst
@@ -0,0 +1,830 @@
+DD 24: Anonymous Age Restriction Extension
+##########################################
+
+Summary
+=======
+
+This document presents and discusses an extension to GNU Taler that provides
+anonymous age-restriction.
+
+Motivation
+==========
+
+Merchants are legally obliged to perform age verification of customers when
+they buy certain goods and services. Current mechanisms for age verification
+are either ID-based or require the usage of credit/debit cards. In all cases
+sensitive private information is disclosed.
+
+We want to offer a better mechanism for age-restriction with GNU Taler that
+
+* ensures anonymity and unlinkability of purchases
+* can be set to particular age groups by parents/wardens at withdrawal
+* is bound to particular coins/tokens
+* can be verified by the merchant at purchase time
+* persists even after refresh
+
+The mechanism is presented as an 'extension' to GNU Taler, that is, as an
+optional feature that can be switched on by the exchange operator.
+
+Requirements
+============
+
+* legal requirements for merchants must allow for this kind of mechanism
+
+
+Proposed Solution
+=================
+
+We propose an extension to GNU Taler for age-restriction that can be enabled by
+an Exchange¹).
+
+Once enabled, coins with age restrictions can be withdrawn by parents/warden
+who can choose to **commit** the coins to a certain maximum age out of a
+predefined list of age groups.
+
+The minors/wards receive those coins and can now **attest** a required minimum
+age (provided that age is less or equal to the committed age of the coins) to
+merchants, who can **verify** the minimum age.
+
+For the rest values (change) after an transaction, the minor/ward can
+**derive** new age-restricted coins. The exchange can **compare** the equality
+of the age-restriction of the old coin with the new coin (in a zero-knowledge
+protocol, that gives the minor/ward a 1/κ chance to raise the minimum age for
+the new coin).
+
+The proposed solution maintains the guarantees of GNU Taler with respect to
+anonymity and unlinkability. We have published a paper
+`Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
+with the details.
+
+¹) Once the feature is enabled and the age groups are defined, the exchange has
+to stick to that decision until the support for age restriction is disabled.
+We might reconsider this design decision at some point.
+
+
+Main ideas and building blocks
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The main ideas are as follows:
+
+#. The exchange defines and publishes M+1 different *age groups* of increasing
+ order: :math:`0 < a_1 < \ldots < a_M` with :math:`a_i \in \mathbb{N}`. The
+ zeroth age group is :math:`\{0,\ldots,a_1-1\}`.
+
+#. An **unrestricted age commitment** is defined as a vector of length M of
+ pairs of Edx25519_ public and private keys on Curve25519. In other words: one
+ key pair for each age group after the zeroth: :math:`\bigl\langle (q_1,
+ p_1), \ldots, (q_M, p_M) \bigr\rangle`. Here, :math:`q_i` are the public keys
+ (mnemonic: **q-mitments**), :math:`p_i` are the private keys.
+
+#. A **restricted age commitment** *to age group m* is derived from an
+ unrestricted age commitment by removing all private keys for
+ indices larger than m: :math:`\bigl\langle (q_1, p_1), \ldots, (q_m, p_m),
+ \, (q_{m+1}, \perp), \ldots, (q_M, \perp )\bigr\rangle`. F.e. if *none* of
+ the private keys is provided, the age commitment would be restricted to the
+ zeroth age group.
+
+#. The act of restricting an unrestricted age commitment is performed by the
+ parent/ward.
+
+#. An *age commitment* (without prefix) is just the vector of public keys:
+ :math:`\vec{Q} := \langle q_1, \ldots, q_M \rangle`. Note that from
+ just the age commitment one can not deduce if it originated from an
+ unrestricted or restricted one (and what age).
+
+#. An *attestation of age group k* is essentially the signature to any message
+ with the private key for slot k, if the corresponding private key is
+ available in a restricted age commitment. (Unrestricted age commitments can
+ attest for any age group).
+
+#. An age commitment is *bound to a particular coin* by incorporating the
+ SHA256 hash value of the age commitment (i.e. the M public keys) into the
+ signature of the coin. So instead of signing :math:`\text{FDH}_N(C_p)` with
+ the RSA private key of a denomination with support for age restriction, we
+ sign :math:`\text{FDH}_N(C_p, h_Q)`. Here, :math:`C_p` is the EdDSA public
+ key of a coin and :math:`h_Q` is the hash of the age commitment :math:`\vec{Q}`.
+ **Note:** A coin with age restriction can only be validated when both, the
+ public key of the coin itself **and** the hash of the age commitment, are
+ present. This needs to be supported in each subsystem: Exchange, Wallet and
+ Merchant.
+
+
+TODO: Summarize the design based on the five functions ``Commit()``,
+``Attest()``, ``Verify()``, ``Derive()``, ``Compare()``, once the paper from
+Özgür and Christian is published.
+
+Changes in the Exchange API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The necessary changes in the exchange involve
+
+* indication of support for age restriction as an extension
+* modification of the refresh protocol (both, commit and reveal phase)
+* modification of the deposit protocol
+
+
+Extension for age restriction
+-----------------------------
+
+.. note::
+
+ Registering an extension is defined in
+ :doc:`design document 006 ― Extensions <006-extensions>`.
+
+
+The exchange indicates support for age-restriction in response to ``/keys`` by
+registering the extension ``age_restriction`` with a value type
+``ExtensionAgeRestriction``:
+
+.. ts:def:: ExtensionAgeRestriction
+
+ interface ExtensionAgeRestriction {
+ // The field ``critical`` is mandatory for an extension.
+ // Age restriction is not required to be understood by an client, so
+ // ``critical`` will be set to ``false``.
+ critical: false;
+
+ // The field ``version`` is mandatory for an extension. It is of type
+ // `LibtoolVersion`.
+ version: "1";
+
+ // Age restriction specific configuration
+ config: ConfigAgeRestriction;
+ }
+
+.. ts:def:: ConfigAgeRestriction
+
+ interface ConfigAgeRestriction {
+ // The age groups. This field is mandatory and binding in the sense
+ // that its value is taken into consideration when signing the
+ // age restricted denominations in the `ExchangeKeysResponse`
+ age_groups: AgeGroups;
+ }
+
+Age Groups
+~~~~~~~~~~
+
+Age groups are represented as a finite list of positive, increasing integers
+that mark the beginning of the *next* age group. The value 0 is omitted but
+implicitly marks the beginning of the *zeroth* age group and the first number
+in the list marks the beginning of the *first* age group. Age groups are
+encoded as a colon separated string of integer values. They are referred to by
+their *slot*, i.e. "age group 3" is the age group that starts with the 3.
+integer in the list.
+
+For example: the string "8:10:12:14:16:18:21" represents the age groups
+
+0. {0,1,2,3,4,5,6,7}
+#. {8,9}
+#. {10,11}
+#. {12,13}
+#. {14,15}
+#. {16,17}
+#. {18,19,20}
+#. {21, ⋯ }
+
+The field ``age_groups`` of type `AgeGroups` is mandatory and binding in the
+sense that its value is taken into consideration when signing the denominations
+in ``ExchangeKeysResponse.age_restricted_denoms``.
+
+.. ts:def:: AgeGroups
+
+ // Representation of the age groups as colon separated edges: Increasing
+ // from left to right, the values mark the beginning of an age group up
+ // to, but not including the next value. The initial age group starts at
+ // 0 and is not listed. Example: "8:10:12:14:16:18:21".
+ type AgeGroups = string;
+
+
+Age restricted denominations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If age-restriction is registered as extension ``age_restriction``, as described
+above, the root-object ``ExchangeKeysResponse`` in response to ``/keys`` MUST
+be extended by an additional field ``age_restricted_denoms``. This is an
+*additional* list of denominations that must be used during the modified
+``refresh`` and ``deposit`` operations (see below).
+
+The data structure for those denominations is the same as for the regular ones
+in ``ExchangeKeysResponse.denoms``. **However**, the following differences
+apply for each denomination in the list:
+
+1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
+ is taken over the public key of the denomination **and** the string in
+ ``ExtensionAgeRestriction.age_groups`` from the corresponding extension
+ object (see above).
+
+2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
+ ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``.
+
+And similar to ``.denoms``, if the query parameter ``last_issue_date`` was
+provided by the client, the exchange will only return the keys that have
+changed since the given timestamp.
+
+
+.. ts:def:: ExchangeKeysResponse
+
+ interface ExchangeKeysResponse {
+ //...
+
+ // List of denominations that support age-restriction with the age groups
+ // given in age_groups. This is only set **iff** the extension
+ // ``age_restriction`` is registered under ``entensions`` with type
+ // ``ExtensionAgeRestriction``.
+ //
+ // The data structure for each denomination is the same as for the
+ // denominations in ExchangeKeysResponse.denoms. **However**, the
+ // following differences apply for each denomination in the list:
+ //
+ // 1. The value of ``TALER_DenominationKeyValidityPS.denom_hash``
+ // is taken over the public key of the denomination __and__ the
+ // string in ``ExtensionAgeRestriction.age_groups`` from the
+ // corresponding extension object.
+ //
+ // 2. The value of ``TALER_DenominationKeyValidityPS.purpose`` is set to
+ // ``TALER_SIGNATURE_MASTER_AGE_RESTRICTED_DENOMINATION_KEY_VALIDITY``
+ //
+ // Similar as for ``.denoms``, if the query parameter ``last_issue_date``
+ // was provided by the client, the exchange will only return the keys that
+ // have changed since the given timestamp.
+ age_restricted_denoms: DenomCommon[];
+
+ //...
+ }
+
+
+SQL schema
+-----------
+
+The exchange has to mark denominations with support for age restriction as such
+in the database. Also, during the melting phase of the refresh operation, the
+exchange will have to persist the SHA256 hash of the age commitment of the
+original coin.
+
+The schema for the exchange is changed as follows:
+
+.. sourcecode:: sql
+
+ -- Everything in one big transaction
+ BEGIN;
+ -- Check patch versioning is in place.
+ SELECT _v.register_patch('exchange-TBD', NULL, NULL);
+
+ -- Support for age restriction is marked per denomination.
+ ALTER TABLE denominations
+ ADD COLUMN age_restricted BOOLEAN NOT NULL DEFAULT (false);
+ COMMENT ON COLUMN denominations.age_restriced
+ IS 'true if this denomination can be used for age restriction';
+
+ -- During the melting phase of the refresh, the wallet has to present the
+ -- hash value of the age commitment (only for denominations with support
+ -- for age restriction).
+ ALTER TABLE refresh_commitments
+ ADD COLUMN age_commitment_h BYTEA CHECK (LENGTH(age_commitment_h)=64);
+ COMMENT ON COLUMN refresh_commitments.age_commitment_h
+ IS 'SHA256 hash of the age commitment of the old coin, iff the corresponding
+ denomimination has support for age restriction, NULL otherwise.';
+ COMMIT;
+
+Note the constraint on ``refresh_commitments.age_commitment_h``: It can be
+NULL, but only iff the corresponding denomination (indirectly referenced via
+table ``known_coins``) has ``.age_restricted`` set to true. This constraint
+can not be expressed reliably with SQL.
+
+
+Protocol changes
+----------------
+
+Withdraw
+~~~~~~~~
+
+The withdraw protocol is affected in the following situations:
+
+- A wire transfer to the exchange (to fill a reserve) was marked by the
+ originating bank as coming from a bank account of a minor, belonging to a of
+ a specific age group, or by other means.
+- A KYC-process has been performed with the owner of a reserve and the user has
+ been identified as being a minor.
+- A Peer-to-Peer transaction was performed between customers. The receiving
+ customer's KYC result tells the exchange that the customer belongs to a
+ specific age group.
+
+In these cases, the wallet will have to perform a zero-knowledge protocol with
+exchange as part of the the withdraw protocol, which we sketch here. Let
+
+- :math:`\kappa` be the same cut-and-choose parameter for the refresh-protocol.
+- :math:`\Omega \in E` be a published, nothing-up-my-sleeve, constant
+ group-element on the elliptic curve.
+- :math:`a \in \{1,\ldots,M\}` be the maximum age (group) for which the wallet
+ has to prove its commitment.
+
+The values :math:`\kappa`, :math:`\Omega` and :math:`a` are known to the
+Exchange and the Wallet. Then, Wallet and Exchange run the following protocol
+for the withdrawal of one coin:
+
+- *Wallet*
+ 1. creates planchets :math:`C_i` for :math:`i \in \{1,\ldots,\kappa\}` as candidates for *one* coin.
+ #. creates age-commitments :math:`\vec{Q}^i` for :math:`i \in \{1,\ldots,\kappa\}` as follows:
+
+ a) creates :math:`a`-many Edx25519-keypairs :math:`(p^i_j, q^i_j)`
+ randomly for :math:`j \in \{1,\ldots,a\}` (with public keys :math:`q^i_j`),
+ #) chooses randomly :math:`(M - a)`-many scalars :math:`s^i_j` for :math:`j \in \{a+1,\ldots,M\}`,
+ #) calculates :math:`\omega^i_j = s^i_j*\Omega` for :math:`j \in \{a+1,\ldots,M \}`,
+ #) sets :math:`\vec{Q}^i := (q^i_1,\ldots,q^i_a,\omega^i_{a+1},\ldots,\omega^i_M)`
+
+ #. calculates :math:`f_i := \text{FDH}(C_i, H(\vec{Q}^i))` for :math:`i \in \{ 1,\ldots,\kappa \}`.
+ #. chooses random blindings :math:`\beta_i(.)` for :math:`i \in \{1,\ldots,\kappa\}`. The blinding functions depend on the cipher (RSA, CS).
+ #. sends :math:`(\beta_1(f_1),\ldots,\beta_\kappa(f_\kappa))` to the Exchange
+
+- *Exchange*
+ 7. receives :math:`(b_1,\ldots,b_\kappa)`
+ #. calculates :math:`F := \text{H}(b_1||\ldots||b_\kappa)`
+ #. chooses randomly :math:`\gamma \in \{1,\ldots,\kappa\}` and
+ #. signs :math:`r := b_\gamma` resulting in signature :math:`\sigma_r`
+ #. stores :math:`F \mapsto (r, \sigma_r)`
+ #. sends :math:`\gamma` to the Wallet.
+
+- *Wallet*
+ 10. receives :math:`\gamma`
+ #. sends to the Exchange the tuple :math:`\left(F, \vec{\beta}, \vec{\vec{Q}}, \vec{\vec{S}}\right)` with
+
+ - :math:`F := \text{H}(\beta_1(f_1)||\ldots||\beta_\kappa(f_\kappa))`
+ - :math:`\vec{\beta} := (\beta_1,\ldots,\beta_{\gamma-1},\bot,\beta_{\gamma+1},\ldots,\beta_\kappa)`
+ - :math:`\vec{\vec{Q}} := (\vec{Q}^1,\ldots,\vec{Q}^{\gamma-1},\bot,\vec{Q}^{\gamma+1},\ldots,\vec{Q}^\kappa)`
+ - :math:`\vec{\vec{S}} := (\vec{S}^1,\ldots,\vec{S}^{\gamma-1},\bot,\vec{S}^{\gamma+1},\ldots,\vec{S}^\kappa)`
+ with :math:`\vec{S}^i := (s^i_j)`
+
+- *Exchange*
+ 12. receives :math:`\left(F, (\beta_i), (\vec{Q}^i), (\vec{B}^i) \right)`
+ #. retrieves :math:`(r, \sigma_r)` from :math:`F` or bails out if not present
+ #. calculates :math:`b_i := \beta_i\left(\text{FDH}(\vec{Q}^i)\right)` for :math:`i \neq \gamma`
+ #. compares :math:`F \overset{?}{=} \text{H}(b_1||\ldots||b_{\gamma - 1}||r||b_{\gamma+1}||\ldots||b_\kappa)` and bails out on inequality
+ #. for each :math:`\vec{B}^i, i \neq \gamma`
+
+ i. calculates :math:`\tilde{\omega}^i_j := b^i_j * \Omega` for :math:`j \in \{a+1,\ldots,M\}`
+ #. 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
+ #. sends (blinded) signature :math:`\sigma_r` to Wallet
+
+- *Wallet*
+ 18. receives :math:`\sigma_r`
+ #. calculates (unblinded) signature :math:`\sigma_\gamma := \beta^{-1}_\gamma(\sigma_r)` for coin :math:`C_\gamma`.
+
+
+Note that the batch version of withdraw allows the withdrawal of *multiple*
+coins at once. For that scenario the protocol sketched above is adapted to
+accomodate for handling multiple coins at once -- thus multiplying the amount
+of data by the amount of coins in question--, but all with the same value of
+:math:`\gamma`.
+
+The *actual* implementation of the protocol above will have major optimizations
+to keep the bandwidth usage to a minimum and also ensure that a denomination in
+the commitment doesn't expire before the reveal.
+
+Instead of generating and sending the age commitment (array of public keys) and
+blindings for each coin, the wallet *MUST* derive the corresponding blindings
+and the age commitments from the coin's private key itself as follows:
+
+Let
+
+- :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
+- :math:`m \in \{1,\ldots,M\}` be the maximum age (according to the reserve)
+ that a wallet can commit to during the withdrawal.
+- :math:`P` be a published constant Edx25519-public-key to which the private
+ key is not known to any client.
+
+For the age commitment, calculate:
+
+1. For age group :math:`a \in \{1,\ldots,m\}`, set
+
+.. math::
+ s_a &:= \text{HDKF}(s, \text{"age-commitment"}, a) \\
+ p_a &:= \text{Edx25519\_generate\_private}(s_a) \\
+ q_a &:= \text{Edx25519\_public\_from\_private}(p_a)
+
+2. For age group :math:`a \in \{m,\ldots,M\}`, set
+
+.. math::
+ f_a &:= \text{HDKF}(s, \text{"age-factor"}, a) \\
+ q_a &:= \text{Edx25519\_derive\_public}(P, f_a).
+
+Then the vector :math:`\vec{q} = \{q_1,\ldots,q_M\}` is then the age commitment
+associated to the coin's private key :math:`c_s`. For the non-disclosed coins,
+the wallet can use the vector :math:`(p_1,\ldots,p_m,\bot,\ldots,\bot)` of
+private keys for the attestation.
+
+Provided with the secret :math:`s`, the exchange can therefore calculate the
+private key :math:`c_s`, the blinding :math:`\beta`, the nonce :math:`n` (if
+needed) and the age commitment :math:`\vec{q}`, along with the coin's public
+key :math:`C_p` and use the value of
+
+.. math::
+
+ \text{TALER\_CoinPubHashP}(C_p, \text{age\_commitment\_hash}(\vec{q}))
+
+during the verification of the original age-withdraw-commitment.
+
+For the withdrawal with age restriction, a sketch of the corresponding database
+schema in the exchange is given here:
+
+.. graphviz::
+
+ digraph deposit_policies {
+ rankdir = LR;
+ splines = true;
+ fontname="monospace"
+ node [
+ fontname="monospace"
+ shape=record
+ ]
+
+ subgraph cluster_commitments {
+ label=<<B>age_withdraw</B>>
+ margin=20
+ commitments [
+ 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"
+ ]
+ }
+
+ commitments:res->reserves:id [ label="n:1"; fontname="monospace"];
+ commitments:denom -> denominations:id [ label="n:1"; fontname="monospace"] ;
+ }
+
+
+Refresh - melting phase
+~~~~~~~~~~~~~~~~~~~~~~~
+
+During the melting phase of the refresh, the wallet has to present the hash
+value of the age commitment (for denominations with support for age
+restriction). Therefore, in the ``/coins/$COIN_PUB/melt`` POST request, the
+``MeltRequest`` object is extended with an optional field
+``age_commitment_hash``:
+
+.. ts:def:: MeltRequest
+
+ interface MeltRequest {
+ ...
+
+ // SHA256 hash of the age commitment of the coin, IFF the denomination
+ // has age restriction support. MUST be omitted otherwise.
+ age_commitment_hash?: AgeCommitmentHash;
+
+ ...
+ }
+
+.. ts:def:: AgeCommitmentHash
+
+ type AgeCommitmentHash = SHA256HashCode;
+
+The responses to the POST request remain the same.
+
+For normal denominations *without* support for age restriction, the calculation
+for the signature check is as before (borrowing notation from
+`Florian's thesis <https://taler.net/papers/thesis-dold-phd-2019.pdf>`_):
+
+.. math::
+ \text{FDH}_N(C_p)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}\,N
+
+Here, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is its
+signature and :math:`\langle e, N \rangle` is the RSA public key of the
+denomination.
+
+For denominations *with* support for age restriction, the exchange takes the
+hash value ``age_commitment_hash`` (abbreviated as :math:`h_a`) into account
+when verifying the coin's signature:
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+
+
+
+Refresh - reveal phase
+~~~~~~~~~~~~~~~~~~~~~~
+
+During the reveal phase -- that is upon POST to ``/refreshes/$RCH/reveal`` --
+the client has to provide the original age commitment of the old coin (i.e. the
+vector of public keys), iff the corresponding denomination had support for age
+restriction. The size of the vector is defined by the Exchange implicetly as
+the amount of age groups defined in the field ``.age_groups`` of the
+``ExtensionAgeRestriction``.
+
+.. ts:def:: RevealRequest
+
+ interface RevealRequest {
+ ...
+
+ // Iff the corresponding denomination has support for age restriction,
+ // the client MUST provide the original age commitment, i.e. the vector
+ // of public keys.
+ // The size of the vector is defined by the Exchange implicetly as the
+ // amount of age groups defined in the field ``.age_groups`` of the
+ // ``ExtensionAgeRestriction``.
+ old_age_commitment?: Edx25519PublicKey[];
+
+
+ ...
+ }
+
+
+The exchange can now check if the provided public keys ``.old_age_commitment``
+have the same SHA256 hash value when hashed in sequence as the
+``age_commitment_hash`` of the original coin from the call to melt.
+
+The existing `cut&choose protocol during the reveal phase
+</core/api-exchange.html#post--refreshes-$RCH-reveal>`__ is extended to perform
+the following additional computation and checks:
+
+Using the κ-1 transfer secrets :math:`\tau_i` from the reveal request, the
+exchange derives κ-1 age commitments from the ``old_age_commitment`` by calling
+``Edx25519_derive_public()`` on each `Edx25519PublicKey`, with :math:`\tau_i`
+as the seed, and then calculates the corresponding κ-1 hash values :math:`h_i`
+of those age commitments.
+
+It then calculates the κ-1 blinded hashes
+:math:`m_i = r^{e_i}\text{FDH}_N(C^{(i)}_p, h_i)` (using the notation from Florian's
+thesis) of the disclosed coins and together with the :math:`m_\gamma` of the
+undisclosed coin, calculates the hash
+:math:`h'_m = H(m_1,\cdots,m_\gamma,\cdots,m_\kappa)` which is then used in the
+final verification step of the cut&choose protocol.
+
+
+Deposit
+~~~~~~~
+
+As always, the merchant has to provide the public key of a coin during a POST
+to ``/coins/$COIN_PUB/deposit``. However, for coins with age restriction, the
+signature check requires the hash of the age commitment. Therefore the request
+object ``DepositRequest`` is extended by an optional field
+``age_commitment_hash`` which MUST be set (with the SHA256 hash of the age
+commitment), iff the corresponding denomination had support for age restriction
+enabled. The merchant has received this value prior from the customer during
+purchase.
+
+.. ts:def:: DepositRequest
+
+ interface DepositRequest {
+ ...
+
+ // Iff the corresponding denomination had support for age restriction
+ // enabled, this field MUST contain the SHA256 value of the age commitment that
+ // was provided during the purchase.
+ age_commitment_hash?: AgeCommitmentHash;
+
+ ...
+ }
+
+Again, the exchange can now check the validity of the coin with age restriction
+by evaluating
+
+.. math::
+ \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+Also again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
+its signature, :math:`\langle e, N \rangle` is the RSA public key of the
+denomination and :math:`h_a` is the value from ``age_commitment_hash``.
+
+
+
+Changes in the Merchant API
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+Claiming the order
+------------------
+
+If an order requires a minimum age, the merchant MUST express that required
+minimum age in response to order claim by the wallet, that is, a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/claim``.
+
+The object ``ContractTerms`` is extended by an optional field
+``minimum_age`` that can be any integer greater than 0. In reality
+this value will not be smaller than, say, 8, and not larger than, say, 21.
+
+.. ts:def:: DD24ContractTerms
+
+ interface DD24ContractTerms {
+ ...
+
+ // If the order requires a minimum age greater than 0, this field is set
+ // to the integer value of that age. In reality this value will not be
+ // smaller than, say, 8, and not larger than, say, 21.
+ minimum_age?: Integer;
+
+ ...
+ }
+
+By sending the contract term with the field ``minimum_age`` set to an
+non-zero integer value, the merchant implicetly signals that it understands the
+extension ``age_restriction`` for age restriction from the exchange.
+
+
+Making the payment
+------------------
+
+If the ``ContractTerms`` had a non-zero value in field
+``minimum_age``, the wallet has to provide evidence of that minimum
+age by
+
+#. *either* using coins which are of denominations that had *no* age support
+ enabled,
+
+#. *or* using coins which are of denominations that have support for age
+ restriction enabled
+
+ * and then ―for each such coin― it has the right private key of the
+ restricted age commitment to the age group into which the required minimum
+ age falls (i.e. a non-empty entry at the right index in vector of Edx25519
+ keys, see above).
+
+ * and signs the required minimum age with each coin's private key
+ corresponding to the age group,
+
+ * and sends ―for each coin― the complete age commitment and the signature to
+ the merchant.
+
+The object ``CoinPaySig`` used within a ``PayRequest`` during a POST to
+``[/instances/$INSTANCE]/orders/$ORDER_ID/pay`` is extended as follows:
+
+.. ts:def:: CoinPaySig
+
+ export interface CoinPaySig {
+ ...
+
+ // If a minimum age was required by the order and the wallet had coins that
+ // are at least committed to the corresponding age group, this is the
+ // signature of the minimum age as a string, using the private key to the
+ // corresponding age group.
+ minimum_age_sig?: Edx25519Signature;
+
+ // If a minimum age was required by the order, this is age commitment bound
+ // to the coin, i.e. the complete vector of Edx25519_ public keys, one for each
+ // age group (as defined by the exchange).
+ age_commitment?: Edx25519PublicKey[];
+
+ }
+
+
+The merchant can now verify
+
+#. the validity of each (age restricted) coin by evaluating
+
+ .. math:: \text{FDH}_N(C_p, h_a)\; \stackrel{?}{=}\; \left(\sigma_C\right)^{e} \;\;\text{mod}N
+
+ Again, :math:`C_p` is the EdDSA public key of a coin, :math:`\sigma_C` is
+ its signature, :math:`\langle e, N \rangle` is the RSA public key of the
+ denomination and :math:`h_a` is the SHA256 hash value of the vector in
+ ``age_commitment``.
+
+#. the minimum age requirement by checking the signature in ``minimum_age_sig``
+ against the public key ``age_commitment[k]`` of the corresponding age group,
+ say, ``k``. (The minimum age must fall into the age group at index ``k`` as
+ defined by the exchange).
+
+**Note**: This applies only to coins for denominations that have support for
+age restriction. Denominations *without* support for age restriction *always*
+satisfy any minimum age requirement.
+
+
+
+Changes in the Wallet
+^^^^^^^^^^^^^^^^^^^^^
+
+A wallet implementation SHOULD support denominations with age restriction. In
+that case it SHOULD allow to select an age group as upper bound during
+withdraw.
+
+
+Alternatives
+============
+
+* ID-based systems
+* credit/debit card based systems
+
+
+Drawbacks
+=========
+
+* age groups, once defined, are set permanently
+
+Also discuss:
+
+* storage overhead
+* computational overhead
+* bandwidth overhead
+* legal issues?
+
+Discussion / Q&A
+================
+
+We had some very engaged discussions on the GNU Taler `mailing list <taler@gnu.org>`__:
+
+* `Money with capabilities <https://lists.gnu.org/archive/html/taler/2021-08/msg00005.html>`_
+
+* `On age-restriction (was: online games in China) <https://lists.gnu.org/archive/html/taler/2021-09/msg00006.html>`__
+
+* `Age-restriction is about coins, not currencies <https://lists.gnu.org/archive/html/taler/2021-09/msg00021.html>`__
+
+* The published paper: `Zero Knowledge Age Restriction for GNU Taler <https://link.springer.com/chapter/10.1007/978-3-031-17140-6_6>`_
+
+
+.. _Edx25519:
+
+Edx25519
+========
+
+Edx25519 is a variant of EdDSA on curve25519 which allows for repeated
+derivation of private and public keys, independently. It is implemented in
+`GNUNET with commit ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32.
+<https://git.gnunet.org/gnunet.git/commit/?id=ce38d1f6c9bd7857a1c3bc2094a0ee9752b86c32>`__
+
+The private keys in Edx25519 initially correspond to the data after expansion
+and clamping in EdDSA. However, this correspondence is lost after deriving
+further keys from existing ones. The public keys and signature verification
+are compatible with EdDSA.
+
+The scheme is as follows:
+
+::
+
+ /* Private keys in Edx25519 are pairs (a, b) of 32 byte each.
+ * Initially they correspond to the result of the expansion
+ * and clamping in EdDSA.
+ */
+
+ Edx25519_generate_private(seed) {
+ /* EdDSA expand and clamp */
+ dh := SHA-512(seed)
+ a := dh[0..31]
+ b := dh[32..64]
+ a[0] &= 0b11111000
+ a[31] &= 0b01111111
+ a[31] |= 0b01000000
+
+ return (a, b)
+ }
+
+ Edx25519_public_from_private(private) {
+ /* Public keys are the same as in EdDSA */
+ (a, _) := private
+ return [a] * G
+ }
+
+ Edx25519_blinding_factor(P, seed) {
+ /* This is a helper function used in the derivation of
+ * private/public keys from existing ones. */
+ h1 := HKDF_32(P, seed)
+
+ /* Ensure that h == h % L */
+ h := h1 % L
+
+ /* Optionally: Make sure that we don't create weak keys. */
+ P' := [h] * P
+ if !( (h!=1) && (h!=0) && (P'!=E) ) {
+ return Edx25519_blinding_factor(P, seed+1)
+ }
+
+ return h
+ }
+
+ Edx25519_derive_private(private, seed) {
+ /* This is based on the definition in
+ * GNUNET_CRYPTO_eddsa_private_key_derive. But it accepts
+ * and returns a private pair (a, b) and allows for iteration.
+ */
+ (a, b) := private
+ P := Edx25519_public_key_from_private(private)
+ h := Edx25519_blinding_factor(P, seed)
+
+ /* Carefully calculate the new value for a */
+ a1 := a / 8;
+ a2 := (h * a1) % L
+ a' := (a2 * 8) % L
+
+ /* Update b as well, binding it to h.
+ This is an additional step compared to GNS. */
+ b' := SHA256(b ∥ h)
+
+ return (a', b')
+ }
+
+ Edx25519_derive_public(P, seed) {
+ h := Edx25519_blinding_factor(P, seed)
+ return [h]*P
+ }
+
+ Edx25519_sign(private, message) {
+ /* As in Ed25519, except for the origin of b */
+ (d, b) := private
+ P := Edx25519_public_from_private(private)
+ r := SHA-512(b ∥ message)
+ R := [r] * G
+ s := r + SHA-512(R ∥ P ∥ message) * d % L
+
+ return (R,s)
+ }
+
+ Edx25519_verify(P, message, signature) {
+ /* Identical to Ed25519 */
+ (R, s) := signature
+ return [s] * G == R + [SHA-512(R ∥ P ∥ message)] * P
+ }