summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2023-01-11 17:33:20 +0100
committerChristian Grothoff <christian@grothoff.org>2023-01-11 17:33:20 +0100
commite6f46fe0a17a3b16289e5c39d8bdc3b8f1e42c3b (patch)
tree61183a733d5d478576067f9874fe8f39cd83a676
parent55e214061688073779f305abe6432c65b718c9a9 (diff)
parent4e9f6c11c7652c30cee8a65e22557f82e36badcf (diff)
downloaddocs-e6f46fe0a17a3b16289e5c39d8bdc3b8f1e42c3b.tar.gz
docs-e6f46fe0a17a3b16289e5c39d8bdc3b8f1e42c3b.tar.bz2
docs-e6f46fe0a17a3b16289e5c39d8bdc3b8f1e42c3b.zip
Merge branch 'master' of git+ssh://git.taler.net/docs
-rw-r--r--core/api-common.rst16
-rw-r--r--core/api-exchange.rst363
-rw-r--r--core/api-merchant.rst39
-rw-r--r--design-documents/024-age-restriction.rst99
4 files changed, 445 insertions, 72 deletions
diff --git a/core/api-common.rst b/core/api-common.rst
index b2913b47..ab158e79 100644
--- a/core/api-common.rst
+++ b/core/api-common.rst
@@ -633,6 +633,7 @@ uses 512-bit hash codes (64 bytes).
struct GNUNET_ShortHashCode hash;
};
+.. _BlindedCoinHash:
.. sourcecode:: c
struct TALER_BlindedCoinHash {
@@ -830,6 +831,21 @@ within the
struct TALER_BlindedCoinHash h_coin_envelope;
};
+.. _TALER_AgeWithdrawRequestPS:
+.. sourcecode:: c
+
+ struct TALER_AgeWithdrawRequestPS {
+ /**
+ * purpose.purpose = TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW
+ */
+ struct GNUNET_CRYPTO_EccSignaturePurpose purpose;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct GNUNET_HashCode age_restricted_coins_commitment;
+ struct GNUNET_HashCode h_denoms_h;
+ uint8 max_age_group;
+ };
+
+
.. _TALER_DepositRequestPS:
.. sourcecode:: c
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
index 6b7031c0..734a17cf 100644
--- a/core/api-exchange.rst
+++ b/core/api-exchange.rst
@@ -132,6 +132,15 @@ possibly by using HTTPS.
// The exchange's signing keys.
signkeys: SignKey[];
+ // Optional field with a dictionary of (name, object) pairs defining the
+ // supported and enabled extensions, such as ``age_restriction``.
+ extensions?: { name: ExtensionManifest };
+
+ // Signature by the exchange master key of the SHA-256 hash of the
+ // normalized JSON-object of field extensions, if it was set.
+ // The signature has purpose TALER_SIGNATURE_MASTER_EXTENSIONS.
+ extensions_sig?: EddsaSignature;
+
// Compact EdDSA `signature` (binary-only) over the SHA-512 hash of the
// concatenation of all SHA-512 hashes of the RSA denomination public keys
// in ``denoms`` in the same order as they were in ``denoms``. Note that for
@@ -1239,6 +1248,14 @@ exchange.
interface ReserveSummary {
// Balance left in the reserve.
balance: Amount;
+
+ // If set, age restriction is required to be set for each coin to this
+ // value during the withdrawal from this reserve. The client then MUST
+ // use a denomination with support for age restriction enabled for the
+ // withdrawal.
+ // The value represents a valid age group from the list of permissible
+ // age groups as defined by the exchange's output to /keys.
+ maximum_age_group?: number;
}
@@ -1283,6 +1300,10 @@ exchange.
// Balance left in the reserve.
balance: Amount;
+ // If set, gives the maximum age group that the client is required to set
+ // during withdrawal.
+ maximum_age_group: number;
+
// Transaction history for this reserve.
// May be partial (!).
history: TransactionHistoryItem[];
@@ -1297,6 +1318,7 @@ exchange.
| AccountSetupTransaction
| ReserveHistoryTransaction
| ReserveWithdrawTransaction
+ | ReserveAgeWithdrawTransaction
| ReserveCreditTransaction
| ReserveClosingTransaction
| ReserveOpenRequestTransaction
@@ -1369,6 +1391,26 @@ exchange.
withdraw_fee: Amount;
}
+ .. ts:def:: ReserveAgeWithdrawTransaction
+
+ interface ReserveAgeWithdrawTransaction {
+ type: "AGEWITHDRAW";
+
+ // Total Amount withdrawn.
+ amount: Amount;
+
+ // Commitment of all ``n*kappa`` coins.
+ age_restricted_coins_commitment: HashCode;
+
+ // Signature over a `TALER_AgeWithdrawRequestPS`
+ // with purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``
+ // created with the reserve's private key.
+ reserve_sig: EddsaSignature;
+
+ // Fee that is charged for withdraw.
+ withdraw_fee: Amount;
+ }
+
.. ts:def:: ReserveCreditTransaction
@@ -1569,6 +1611,68 @@ exchange.
}
+.. _delete-reserve:
+
+.. http:DELETE:: /reserves/$RESERVE_PUB
+
+ Forcefully closes a reserve.
+ The request header must contain an *Account-Request-Signature*.
+ Note: this endpoint is not currently implemented!
+
+ **Request:**
+
+ *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``.
+
+ :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange
+ will delete the account even if there is a balance remaining.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The operation succeeded, the exchange provides details
+ about the account deletion.
+ The response will include a `ReserveClosedResponse` object.
+ :http:statuscode:`403 Forbidden`:
+ The *Account-Request-Signature* is invalid.
+ This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`404 Not found`:
+ The account is unknown to the exchange.
+ :http:statuscode:`409 Conflict`:
+ The account is still has digital cash in it, the associated
+ wire method is ``void`` and the *force* option was not provided.
+ This response comes with a standard `ErrorDetail` response.
+
+ **Details:**
+
+ .. ts:def:: ReserveClosedResponse
+
+ interface ReserveClosedResponse {
+
+ // Final balance of the account.
+ closing_amount: Amount;
+
+ // Current time of the exchange, used as part of
+ // what the exchange signs over.
+ close_time: Timestamp;
+
+ // Hash of the wire account into which the remaining
+ // balance will be transferred. Note: may be the
+ // hash over ``payto://void/`, in which case the
+ // balance is forfeit to the profit of the exchange.
+ h_wire: HashCode;
+
+ // This is a signature over a
+ // struct ``TALER_AccountDeleteConfirmationPS`` with purpose
+ // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
+ exchange_sig: EddsaSignature;
+
+ }
+
+
+
+Withdraw
+~~~~~~~~
+
.. http:post:: /csr-withdraw
Obtain exchange-side input values in preparation for a
@@ -1638,6 +1742,7 @@ exchange.
r_pub_1: CsRPublic;
}
+
.. http:post:: /reserves/$RESERVE_PUB/withdraw
Withdraw a coin of the specified denomination. Note that the client should
@@ -1830,6 +1935,10 @@ exchange.
+Batch Withdraw
+~~~~~~~~~~~~~~
+
+
.. http:post:: /reserves/$RESERVE_PUB/batch-withdraw
Withdraw multiple coins from the same reserve. Note that the client should
@@ -1844,10 +1953,9 @@ exchange.
:http:statuscode:`200 OK`:
The request was successful, and the response is a `BatchWithdrawResponse`.
- Note that repeating exactly the same request
- will again yield the same response, so if the network goes down during the
- transaction or before the client can commit the coin signature to disk, the
- coin is not lost.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
:http:statuscode:`403 Forbidden`:
A signature is invalid.
This response comes with a standard `ErrorDetail` response.
@@ -1862,14 +1970,14 @@ exchange.
In this case, the wallet should repeat the exact same request later again
using exactly the same blinded coin.
:http:statuscode:`409 Conflict`:
- The balance of the reserve is not sufficient to withdraw the coins of the indicated denominations.
- The response is `WithdrawError` object.
+ The balance of the reserve is not sufficient to withdraw the coins of the
+ indicated denominations. The response is `WithdrawError` object.
:http:statuscode:`410 Gone`:
A requested denomination key is not yet or no longer valid.
- It either before the validity start, past the expiration or was revoked. The response is a
- `DenominationExpiredMessage`. Clients must evaluate
- the error code provided to understand which of the
- cases this is and handle it accordingly.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
:http:statuscode:`451 Unavailable for Legal Reasons`:
This reserve has received funds from a purse or the amount withdrawn
exceeds another legal threshold and thus the reserve must
@@ -1900,6 +2008,7 @@ exchange.
}
+
.. ts:def:: BatchWithdrawResponse
interface BatchWithdrawResponse {
@@ -1909,64 +2018,211 @@ exchange.
}
+Withdraw with Age Restriction
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. _delete-reserve:
-
-.. http:DELETE:: /reserves/$RESERVE_PUB
+If the reserve was marked with a maximum age group, the client has to perform a
+cut&choose protocol with the exchange. It first calls
+``/reserves/$RESERVE_PUB/age-withdraw`` and commits to ``n*kappa`` coins. On
+success, the exchange answers this request with an noreveal-index. The client
+then has to call ``/age-withdraw/$ACH/reveal`` to reveal all ``n*(kappa - 1)``
+coins along with their age commitments to proof that they were appropriate.
+If so, the exchange will blindly sign ``n`` undisclosed coins from the request.
- Forcefully closes a reserve.
- The request header must contain an *Account-Request-Signature*.
- Note: this endpoint is not currently implemented!
- **Request:**
+.. http:POST:: /reserves/$RESERVE_PUB/age-withdraw
- *Account-Request-Signature*: The client must provide Base-32 encoded EdDSA signature made with ``$ACCOUNT_PRIV``, affirming its authorization to delete the account. The purpose used MUST be ``TALER_SIGNATURE_RESERVE_CLOSE``.
+ Withdraw multiple coins *with age restriction* from the same reserve.
+ Note that the client should commit all of the request details, including the
+ private key of the coins and the blinding factors, to disk *before* issuing
+ this request, so that it can recover the information if necessary in case of
+ transient failures, like power outage, network outage, etc.
- :query force=BOOLEAN: *Optional.* If set to 'true' specified, the exchange
- will delete the account even if there is a balance remaining.
+ **Request:** The request body must be a `AgeWithdrawRequest` object.
**Response:**
:http:statuscode:`200 OK`:
- The operation succeeded, the exchange provides details
- about the account deletion.
- The response will include a `ReserveClosedResponse` object.
+ The request was successful, and the response is a `AgeWithdrawResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
:http:statuscode:`403 Forbidden`:
- The *Account-Request-Signature* is invalid.
+ A signature is invalid.
This response comes with a standard `ErrorDetail` response.
+ :http:statuscode:`409 Conflict`:
+ The balance of the reserve is not sufficient to withdraw the coins of the
+ given amount. The response is a `WithdrawError` object.
+ :http:statuscode:`410 Gone`:
+ A requested denomination key is not yet or no longer valid.
+ It either before the validity start, past the expiration or was revoked.
+ The response is a `DenominationExpiredMessage`. Clients must evaluate the
+ error code provided to understand which of the cases this is and handle it
+ accordingly.
+ :http:statuscode:`451 Unavailable for Legal Reasons`:
+ This reserve has received funds from a purse or the amount withdrawn
+ exceeds another legal threshold and thus the reserve must
+ be upgraded to an account (with KYC) before the withdraw can
+ complete. Note that this response does NOT affirm that the
+ withdraw will ultimately complete with the requested amount.
+ The user should be redirected to the provided location to perform
+ the required KYC checks to open the account before withdrawing.
+ Afterwards, the request should be repeated.
+ The response will be an `KycNeededRedirect` object.
+
+ .. ts:def:: AgeWithdrawRequest
+
+ interface AgeWithdrawRequest {
+ // Commitment to the coins with age restriction. This is the SHA512
+ // hash value $ACH over all n*kappa `BlindedCoinHash` values of all
+ // coins and their age commitments. It is alter used as part of the URL
+ // in the subsequent call to /age-withdraw/$ACH/reveal.
+ age_restricted_coins_commitment: HashCode;
+
+ // The total amount that the client wants to withdraw from the reserve
+ // and must be at most the balance of the reserve. The balance of the
+ // reserve will be immediatley reduced by that amount.
+ // In the subsequent call to /age-withdraw/$ACH/reveal, the client has to
+ // provide the list of denominations (with support for age restriction)
+ // that the coins shall be signed with. The sum of the values of those
+ // denominations MUST equal this amount.
+ amount: Amount;
+
+ // The maximum age group to commit to. MUST be the same as the maximum
+ // age group in the reserve.
+ max_age_group: number;
+
+ // Signature of `TALER_AgeWithdrawRequestPS` created with
+ // the `reserves's private key <reserve-priv>`
+ // using purpose ``TALER_SIGNATURE_WALLET_RESERVE_AGE_WITHDRAW``.
+ reserve_sig: EddsaSignature;
+ }
+
+ .. ts:def:: AgeWithdrawResponse
+
+ interface AgeWithdrawResponse {
+ // index of the commitments that the client doesn't
+ // have to disclose
+ noreveal_index: Integer;
+
+ // Signature of `TALER_AgeWithdrawRequestPS` whereby
+ // the exchange confirms the ``noreveal_index``.
+ exchange_sig: EddsaSignature;
+
+ // `Public EdDSA key <sign-key-pub>` of the exchange that was used to
+ // generate the signature. Should match one of the exchange's signing
+ // keys from ``/keys``. Again given explicitly as the client might
+ // otherwise be confused by clock skew as to which signing key was used.
+ exchange_pub: EddsaPublicKey;
+ }
+
+
+
+.. http:POST:: /age-withdraw/$ACH/reveal
+
+ The client has previously committed to multiple coins with age restriction
+ in a call to ``/reserve/$RESERVE_PUB/age-withdraw`` and got a
+ `AgeWithdrawResponse` from the exchange. By calling this
+ endpoint, the client has to reveal each coin and their ``kappa - 1``
+ age commitments, except for the age commitments with index
+ ``noreveal_index``. The hash of all commitments from the former withdraw
+ request is given as the ``$ACH`` value in the URL to this endpoint.
+
+
+ **Request:** The request body must be a `AgeWithdrawRevealRequest` object.
+
+ **Response:**
+
+ :http:statuscode:`200 OK`:
+ The request was successful, and the response is a `BlindedSignaturesResponse`.
+ Note that repeating exactly the same request will again yield the same
+ response, so if the network goes down during the transaction or before the
+ client can commit the coin signature to disk, the coin is not lost.
:http:statuscode:`404 Not found`:
- The account is unknown to the exchange.
+ The provided commitment $ACH is unknown.
:http:statuscode:`409 Conflict`:
- The account is still has digital cash in it, the associated
- wire method is ``void`` and the *force* option was not provided.
- This response comes with a standard `ErrorDetail` response.
+ The reveal operation failed and the response is an `WithdrawError` object.
+ The error codes indicate one of two cases:
- **Details:**
+ 1. An age commitment for one of the coins did not fulfill the required
+ maximum age requirement of the corresponding reserve. Error code:
+ ``TALER_EC_EXCHANGE_GENERIC_COIN_AGE_REQUIREMENT_FAILURE``.
+ 2. The sum of all denominations in the request is not equal to the amount
+ that was given in the previous commitment via the call to
+ /reserves/$RESERVE_PUB/age-withdraw. Error code:
+ ``TALER_EC_EXCHANGE_GENERIC_MISMATCH_OF_AMOUNT_AND_DENOMINATIONS``.
- .. ts:def:: ReserveClosedResponse
- interface ReserveClosedResponse {
+ .. ts:def:: AgeWithdrawRevealRequest
- // Final balance of the account.
- closing_amount: Amount;
+ interface AgeWithdrawRevealRequest {
+ // The public key of the reserve that was used for the initial commitment
+ // request. Needed for optimized database lookup.
+ reserve_pub: EddsaPublicKey;
- // Current time of the exchange, used as part of
- // what the exchange signs over.
- close_time: Timestamp;
+ // Array of ``n`` hash codes of denomination public keys to order.
+ // These denominations MUST support age restriction as defined in the
+ // output to /keys.
+ // The sum of all denomination's values MUST equal the original amount
+ // of the previous commitment.
+ denoms_h: HashCode[];
- // Hash of the wire account into which the remaining
- // balance will be transferred. Note: may be the
- // hash over ``payto://void/`, in which case the
- // balance is forfeit to the profit of the exchange.
- h_wire: HashCode;
+ // Array of ``n`` entries with blinded coins, which are the non-desclosed
+ // coins in the previous commitment. They match the respective entries
+ // in ``denoms_h``.
+ coin_evs: CoinEnvelope[];
- // This is a signature over a
- // struct ``TALER_AccountDeleteConfirmationPS`` with purpose
- // ``TALER_SIGNATURE_EXCHANGE_RESERVE_CLOSED``.
- exchange_sig: EddsaSignature;
+ // Array of ``n`` arrays of ``kappa - 1`` disclosed coin private keys,
+ // from which the associated age commitments are also derived.
+ disclosed_coins: DisclosedAgeRestrictedCoin[][];
+
+ }
+
+ .. ts:def:: DisclosedAgeRestrictedCoin
+
+ interface DisclosedAgeRestrictedCoin {
+ // A coin's private key. The associated blinding and age commitment for
+ // this coin MUST be derived from this private key as follows:
+ //
+ // Calculate the blinding beta as
+ // beta := HKDF(coin_priv, "blinding")
+ //
+ // If the denominations are for Clause-Schnorr-Signatures, calculate the
+ // nonce as
+ // nonce := HKDF(coin_priv, "cs-nonce")
+ //
+ // Let m ∈ {1,...,M} be the maximum age group as defined in the reserve
+ // that the wallet can commit to.
+ //
+ // For age group $AG ∈ {1,...m}, set
+ // seed = HDKF(coin_priv, "age-commitment", $AG)
+ // p[$AG] = Edx25519_generate_private(seed)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_public_from_private(p[$AG])
+ //
+ // For age groups $AG ∈ {m,...,M}, set
+ // f[$AG] = HDKF(coin_priv, "age-factor", $AG)
+ // and calculate the corresponding Edx25519PublicKey as
+ // q[$AG] = Edx25519_derive_public(`PublishedAgeRestrictionBaseKey`, f[$AG])
+ //
+ // Finally, with coin_priv and age commitment (q[]), the exchange
+ // will calculate the coin's public key coin_pub and use the
+ // TALER_CoinPubHashP(coin_pub, age_commitment_hash(q))
+ // during the verification of the original age-withdraw-commitment.
+ coin_priv: EddsaPrivateKey;
}
+ .. ts:def:: PublishedAgeRestrictionBaseKey
+
+ // The value for ``PublishedAgeRestrictionBaseKey`` is a randomly chosen
+ // `Edx25519PublicKey` for which the private key is not known to the clients. It is
+ // used during the age-withdraw protocol so that clients can proof that they
+ // derived all public keys to age groups higher than their allowed maximum
+ // from this particular value.
+ const PublishedAgeRestrictionBaseKey =
+ new Edx25519PublicKey("DZJRF6HXN520505XDAWM8NMH36QV9J3VH77265WQ09EBQ76QSKCG");
+
.. _deposit-par:
@@ -2060,6 +2316,12 @@ proof to the seller for the escrow of sufficient fund.
// Hash of denomination RSA key with which the coin is signed.
denom_pub_hash: HashCode;
+ // IFF the corresponding denomination has support for
+ // age restriction enabled, this field MUST contain the SHA256
+ // value of the age commitment that MUST have been provided during the
+ // purchase.
+ age_commitment_hash?: AgeCommitmentHash;
+
// Exchange's unblinded RSA signature of the coin.
ub_sig: DenominationSignature;
@@ -2922,6 +3184,10 @@ the API during normal operation.
// denominations is of type Clause-Schnorr.
rms?: RefreshMasterSeed;
+ // IFF the denomination has age restriction support, the client MUST
+ // provide the SHA256 hash of the age commitment of the coin.
+ // MUST be omitted otherwise.
+ age_commitment_hash?: AgeCommitmentHash;
}
For details about the HKDF used to derive the new coin private keys and
@@ -3044,6 +3310,13 @@ the API during normal operation.
// Signs over a `TALER_CoinLinkSignaturePS`.
link_sigs: EddsaSignature[];
+ // 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 MUST be the number of age groups as defined by the
+ // Exchange in the field ``.age_groups`` of the extension ``age_restriction``.
+ old_age_commitment?: Edx25519PublicKey[];
+
}
diff --git a/core/api-merchant.rst b/core/api-merchant.rst
index ff03795a..f686b302 100644
--- a/core/api-merchant.rst
+++ b/core/api-merchant.rst
@@ -1575,7 +1575,7 @@ Inspecting inventory
next_restock?: Timestamp;
// Minimum age buyer must have (in years).
- minimum_age: Integer;
+ minimum_age?: Integer;
}
@@ -3097,34 +3097,27 @@ Inspecting template
:http:statuscode:`404 Not found`:
The backend has does not know about the instance.
-
.. ts:def:: TemplateSummaryResponse
interface TemplateSummaryResponse {
- // List of templates that are present in our backend.
- templates_list: TemplateEntry[];
- }
-
-
+ // List of templates that are present in our backend.
+ templates_list: TemplateEntry[];
+ }
The `TemplatesEntry` object describes a template. It has the following structure:
-
-
.. ts:def:: TemplateEntry
interface TemplateEntry {
- // Template identifier, as found in the template.
- template_id: string;
-
- // Human-readable description for the template.
- template_description: string;
-
- }
+ // Template identifier, as found in the template.
+ template_id: string;
+ // Human-readable description for the template.
+ template_description: string;
+ }
.. http:get:: [/instances/$INSTANCE]/private/templates/$TEMPLATE_ID
@@ -3206,21 +3199,15 @@ Using template
// The amount entered by the customer.
amount?: Amount;
- }
-
+ }
.. ts:def:: UsingTemplateResponse
interface UsingTemplateResponse {
- // After enter the request. The user will be pay with a taler URL.
- taler_url: string;
- }
-
-
-
-
-
+ // After enter the request. The user will be pay with a taler URL.
+ taler_url: string;
+ }
--------
diff --git a/design-documents/024-age-restriction.rst b/design-documents/024-age-restriction.rst
index e2161688..5b6e091d 100644
--- a/design-documents/024-age-restriction.rst
+++ b/design-documents/024-age-restriction.rst
@@ -53,7 +53,7 @@ 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
+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.
@@ -291,6 +291,7 @@ 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
----------------
@@ -371,6 +372,102 @@ 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 a major optimization
+to keep the bandwidth usage to a minimum. 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:`c_s` be the private key of the coin,
+- :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.
+
+
+Then calculate the blinding :math:`\beta` for the coin as
+
+.. math::
+ \beta &:= \text{HKDF}(c_s, \text{"blinding"})
+
+If the denomination is using Clause-Schnorr signatures, calculate the nonce
+:math:`n` for the coin as
+
+.. math::
+ n &:= \text{HKDF}(c_s, \text{"cs-nonce"})
+
+
+
+For the age commitment, calculate:
+
+1. For age group :math:`a \in \{1,\ldots,m\}`, set
+
+.. math::
+ s_a &:= \text{HDKF}(c_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}(c_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 private key :math:`c_s`, the exchange can therefore calculate
+the blinding :math:`\beta`, the nonce :math:`n` (if needed) and the age
+commitment :math:`\vec{q}` itself, 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>withdraw_age_commitments</B>>
+ margin=20
+ commitments [
+ label="<id>withdraw_age_commitments_id\l|h_commitment\l|amount_with_fee_val\l|amount_with_fee_frac\l|noreveal_index\l|max_age_group\l|<res>reserve_pub\l|reserve_sig\l|timestamp\l"
+ ]
+ }
+
+ subgraph cluster_reveals {
+ label=<<B>withdraw_age_reveals</B>>
+ margin=20
+ reveals [
+ label="freshcoin_index\l|<comm>withdraw_age_commitments_id\l|<denom>denominations_serial\l|h_coin_ev\l"
+ ]
+ }
+
+ commitments:res->reserves:id [ label="n:1"; fontname="monospace"];
+ reveals:comm -> commitments:id [ label="n:1"; fontname="monospace" ];
+ reveals:denom -> denominations:id [ label="n:1"; fontname="monospace"] ;
+
+ }
+
Refresh - melting phase
~~~~~~~~~~~~~~~~~~~~~~~