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 }