diff options
Diffstat (limited to 'design-documents/024-age-restriction.rst')
-rw-r--r-- | design-documents/024-age-restriction.rst | 830 |
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 + } |