taler-docs

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

062-pq-refresh.rst (15311B)


      1 DD 62: PQ Refresh Protocol
      2 ##########################
      3 
      4 :Design status: Superseded
      5 :Implementation status: Implemented
      6 :DD shepherd: TBD
      7 :Historical contributors: Özgür Kesim, Christian Grothoff
      8 :First published: 2025-04-12
      9 :Last substantive change: 2025-11-25
     10 :Implementation evidence: exchange (2025-04-14)
     11 :Superseded by: Exchange protocol v32 refresh (vDOLDPLUS)
     12 :Normative references: ``core/exchange/post-melt.rst``, ``core/exchange/post-reveal-melt.rst``
     13 
     14 .. warning::
     15 
     16    This signature-derived refresh design was implemented and then superseded.
     17    It is non-normative and conflicts with protocol v32, which uses ECDHE
     18    transfer public keys and revealed batch seeds.  Implementations must follow
     19    the current exchange API.
     20 
     21 Summary
     22 =======
     23 This document specifies a change to GNU Taler's refresh protocol that provides
     24 post-quantum resistance through hash-based cryptography and unique
     25 signatures, eliminating reliance on Diffie-Hellman operations, for the key
     26 derivation for the fresh coin from a dirty coin.
     27 
     28 Motivation
     29 ==========
     30 
     31 The current refresh protocol uses cryptographic primitives vulnerable to
     32 quantum attacks to derive the key material for a fresh coin from the key of the
     33 dirty coin.  The rational for the elaborate derivation in first place is to
     34 ensure taxability:  When original and new coins remain linked (by the owner of
     35 the original coin), passing coin outside the Taler protocols is discouraged.
     36 
     37 This redesign:
     38 
     39 1. Removes DH operations from refresh derivation
     40 2. Uses unique signatures for ownership proofs
     41 3. Derives key material from (unforgeable) signatures
     42 4. Maintains backward compatibility with other parts of the protocol stack
     43 
     44 Requirements
     45 ============
     46 
     47 * PQ-resistant refresh key derivation without computational hardness assumptions
     48 * Preservation of unlinkability between old/new coins (except for coin owner)
     49 * Compatibility with existing denomination types
     50 * Minimal bandwidth/storage overhead
     51 
     52 Proposed Solution
     53 =================
     54 
     55 RefreshDerive Algorithm
     56 ^^^^^^^^^^^^^^^^^^^^^^^^^
     57 
     58 The core mechanism uses two hash functions and unique signatures to
     59 derive the key material of a fresh coin from the old coin:
     60 
     61 .. sourcecode:: python
     62 
     63     # Notation:
     64     # r = random seed, cs = dirty coin secret, Cp = dirty coin public key
     65     # pkD = denomination public key
     66 
     67     def RefreshDerive(r, cs, Cp, pkD):
     68         t = Hash1a("Refresh", Cp, r)
     69         s = SignUnique(cs, t)
     70         x = Hash1b(s)
     71         b = Hash2(s)
     72         c2_s, C2_p = KeyGen(x)
     73         m = Blind(C2_p, b, pkD)
     74         return (s, c2_s, C2_p, m)
     75 
     76 
     77 Key Changes to the existing ``RefreshDerive``:
     78   1. *Proof of ownership*: ``s`` proves ownership through signature, without DH
     79   2. *Key derivation*: ``x`` derived through hashing of the signature
     80 
     81 The hash functions ``Hash1x`` might be the same, but can be pair-wise
     82 different.  However, the hash function ``Hash2`` must be different from
     83 all of the others.
     84 
     85 Note that the value ``r`` in the algorithm itself is a public value and can be
     86 considered as a kind of a commitment ("I'm going to sign this value") by the
     87 dirty coin.  Actual *secret* is the signature which needs to be disclosed
     88 during the reveal-part of the refresh operation.
     89 
     90 A variant of this algorithm that is suitable for retrieving a batch of ``n``
     91 fresh coins from a dirty coin is as follows:
     92 
     93 .. sourcecode:: python
     94 
     95     # Notation:
     96     # r = random dees, cs = dirty coin's secret, Cp = dirty coin's public key
     97     # pkD[] = array of denomination public keys,
     98     # meta = additional information, f.e. the index in a cut-and-choose
     99     def RefreshDeriveBatch(r, cs, Cp, pkDs: list[denomPublicKey], meta):
    100         t = Hash1a("Refresh", Cp, r, pkDs, meta)
    101         s = SignUnique(cs, t)
    102         for i, pkD in enumerate(pkDs):
    103             x[i] = Hash1b(s, i) # Note: use one HKDF for all i
    104             b[i] = Hash2(s, i)  # Note: use one HKDF for all i
    105         for i, pkD in enumerate(pkDs):
    106             c2_s[i], C2_p[i] = KeyGen(x[i])
    107             m[i] = Blind(C2_p[i], b[i], pkD)
    108         return (s, c2_s, C2_p, m)
    109 
    110 Note that the above deriviation will need to be done ``κ`` times with
    111 ``κ - 1`` of the signatures ``s`` being checked by the exchange as part of
    112 the reveal state of the cut-and-choose protocol.  Again, each of the ``κ``
    113 values ``r`` is a public value and can be considered as a kind of a commitment
    114 ("I'm going to sign this value") by the dirty coin.  The actual *secrets* are
    115 the ``κ`` signatures ``s`` which need to be disclosed during the
    116 reveal-part of the refresh operation.
    117 
    118 
    119 Protocol Modifications
    120 ^^^^^^^^^^^^^^^^^^^^^^
    121 
    122 Here is a short description of the main steps, with only one fresh coin being
    123 requested.  We will provide further details, once the related paper [1]_ is
    124 published.
    125 
    126 1. **Melting/Commit Phase**:
    127 
    128    - Client chooses a master (public) seed ``r`` and derives ``κ`` nonces ``r_1, ... r_κ``.
    129    - Client generates, using ``RefreshDeriveBatch``, ``κ*n`` blinded coin planchets
    130      ``m[1][1],...,m[1][n],...,m[κ][1],...,m[κ][n]`` from the nonces
    131    - Sends dirty coin public key ``Cp``, seed ``r``, all ``m[i][j]`` and
    132      fresh coin denomination selections ``pkD[1],...pkD[n]`` to the exchange,
    133      with signature ``σ_c`` made with the dirty coins' private key ``cs`` over the request.
    134    - Exchange verifies the request.
    135    - Exchange calculates ``h_m[i] = H(m[i][1],...,m[i][n])`` for all ``i`` from ``1...κ``
    136    - Exchange calculates ``H_m = H(h_m[1],...,h_m[κ])``
    137    - Exchange calculates ``rc = H(r, pkD[], H_m, meta, <maybe more>)``
    138    - Exchange chooses ``γ`` from ``1...κ`` and signs all ``m[γ][]``,
    139      resulting in ``σ[γ][]``.  This is done now as the exchange may later
    140      have deleted (or lost) its private signing key.
    141    - Exchange persists ``rc → (r, γ, pkD[], H_m, h_m[γ], σ[γ][], σ_c)``,
    142      deducts the cost for the operation from the old coin balance
    143      (in the same database transaction) and returns ``γ`` to the client.
    144 
    145 2. **Reveal Phase**:
    146 
    147    - Client discloses together with ``h_m`` all except the ``γ``-th
    148      (secret) signatures ``s[1],...,s[κ]`` from the ``κ`` calls to
    149      ``RefreshDeriveBatch``.
    150    - Exchange derives ``r_i`` from ``r`` and verifies each signature
    151      ``s[i]`` over ``Hash1a("Refresh", C_p, r_i, pkDs)``.
    152    - Exchange reconstructs the blinded coins ``m'[i][]`` for ``i != γ``.
    153    - Exchange calculates ``h'_m[i] = H(m'[i][])`` for all ``i != γ``.
    154    - Exchange calculates ``H'_m = H(h'_m[1],...,h_m[γ],...h'_m[κ])``.
    155    - Exchange verifies ``rc == H(r, pkD[], H'_m, ...)`` equality.
    156    - Exchange returns ``σ[γ][]`` on success.
    157 
    158 It is worth noting that, in contrast to the existing refresh protocol, the
    159 client sends all ``n*κ`` tuples ``m[][]`` already in the commitment phase.  This is
    160 necessary such that the exchange can sign the request with a valid denomination
    161 key *at the moment of melting*.  This ensures idempotency of the melting/commit
    162 request and that caries over to the reveal phase.
    163 
    164 Also note, as described above, the master seed ``r`` for the refresh operation
    165 is a **public** value and can be considered a commitment ("I'm going to sign
    166 this value") made by the dirty coin.  The actual *secrets* are the signatures
    167 which are reveal in the second phase.
    168 
    169 Note that for the Linking protocol, given the dirty coin's public key, the
    170 Exchange simply returns the master seed ``r`` and the dirty coins' signature
    171 ``σ_c`` over the original refresh request.  The owner of the private key of
    172 the dirty coin can then replay the refresh protocol and can be sure that the
    173 master seed was of its own origin.  Furthermore, the coin history endpoint
    174 must already return this information (so that clients can verify the old coin
    175 history about the refresh), thus obsoleting the need for a separate "/link"
    176 endpoint.
    177 
    178 
    179 Database Changes
    180 ^^^^^^^^^^^^^^^^
    181 
    182 Not taking sharding and coinstraints into account, the table layout will look
    183 basically like this (names might change):
    184 
    185 .. table:: SQL table layout for refresh
    186    :align: left
    187 
    188    ==============  ============  ================================================
    189    Field           Type          Description
    190    ==============  ============  ================================================
    191    refresh_id      BIGINT        autoincremented identity of the record
    192    rc              BYTEA         refresh commitment ``h_m``, serving as primary key
    193    timestamp       INT8          execution date of the refresh
    194    amount          taler_amount  amount with fee of the refresh
    195    old_coin_pub    BYTEA         old coin's public key ``Cp``
    196    old_coin_sig    BYTEA         old coin's signature over the refresh request
    197    old_age_com_h   BYTEA         old coin's hash of age commitment, if applicable
    198    noreveal_index  SMALLINT      the ``γ`` for cut-and-choose, chosen by the exchange
    199    planchets_h     BYTEA         the hash over *all* blinded coin envelopes ``m[][]``
    200    selected_h      BYTEA         the hash over only the *selected* blinded envelopes ``m[γ][]``
    201    refresh_seed    BYTEA         the master seed for the refresh, the ``r`` above
    202    blinding_seed   BYTEA         the master seed for CS nonces
    203    cs_r_values     BYTEA[]       the pairs of R-Values for CS signatures
    204    cs_r_choices    INT8          the bitvector representing the chosen public R-Values
    205    denom_serials   INT8[]        the row ID's of the denominations in the DB
    206    denom_sigs      BYTEA[]       the (blinded) denom signatures ``σ[γ][]``
    207    ==============  ============  ================================================
    208 
    209 
    210 API endpoints
    211 ^^^^^^^^^^^^^^
    212 
    213 A new ``/melt`` request takes a `NewMeltRequest` as request body, see below.
    214 As in the existing melting/commit phase, it updates the old coin balance and
    215 chooses a random ``γ`` for the cut-and-choose protocol.  Taler uses a global
    216 parameter ``κ`` for the cut-and-choose component of the protocol, for which
    217 this request is the commitment.  Thus, various arguments are given ``κ``-times
    218 in this step.  At present ``κ`` is always 3.
    219 
    220   :http:statuscode:`200 OK`:
    221     The request was successful.  The response body is `MeltResponse` in this case.
    222   :http:statuscode:`403 Forbidden`:
    223     One of the signatures (by the old coin or the denomination signature over
    224     the old coin) is invalid.
    225   :http:statuscode:`404 Not found`:
    226     The exchange does not recognize the denomination key as belonging to the exchange,
    227     or it is past the legal expiration time (and thus forgotten entirely).
    228     If the denomination key is unknown, the response will be
    229     a `DenominationUnknownMessage`.
    230   :http:statuscode:`409 Conflict`:
    231     The operation is not allowed as the coin has insufficient
    232     residual value, or because the same public key of the coin has been
    233     previously used with a different denomination.  Which case it is
    234     can be decided by looking at the error code
    235     (``TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS`` or
    236     ``TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY``).
    237     The response is `MeltForbiddenResponse` in both cases.
    238   :http:statuscode:`410 Gone`:
    239     The requested denomination key is not yet or no longer valid.
    240     It either before the validity start, past the expiration or was revoked.
    241     The response is a `DenominationGoneMessage`.
    242     Clients must evaluate the error code provided to understand which of the
    243     cases this is and handle it accordingly.
    244 
    245 
    246 Wire Formats
    247 ^^^^^^^^^^^^
    248 
    249 Modified melt request structure:
    250 
    251 .. ts:def:: NewMeltRequest
    252 
    253     interface NewMeltRequest {
    254        // The old coin's public key
    255        old_coin_pub: CoinPublicKey;
    256 
    257        // Hash of the denomination public key of the old coin, to determine total coin value.
    258        old_denom_pub_h: HashCode;
    259 
    260        // The hash of the age-commitment for the old coin. Only present
    261        // if the denomination has support for age restriction.
    262        old_age_commitment_h?: AgeCommitmentHash;
    263 
    264        // Signature over the old `coin public key <eddsa-coin-pub>` by the denomination.
    265        old_denom_sig: DenominationSignature;
    266 
    267        // Amount of the value of the old coin that should be melted as part of
    268        // this refresh operation, including melting fee.
    269        value_with_fee: Amount;
    270 
    271        // Array of ``n`` new hash codes of denomination public keys
    272        // for the new coins to order.
    273        denoms_h: HashCode[];
    274 
    275        // Seed from which the nonces for the ``n*kappa`` coin candidates are derived
    276        // from.
    277        refresh_seed: HashCode;
    278 
    279        // Master seed for the Clause-Schnorr R-value
    280        // creation. Must match the /blinding-prepare request.
    281        // Must not have been used in any prior melt request.
    282        // Must be present if and only if one of the fresh coin's
    283        // denominations is of type Clause-Schnorr.
    284        blinding_seed?: BlindingMasterSeed;
    285 
    286        // kappa arrays of ``n`` entries for blinded coin candidates,
    287        // each matching the respective entries in ``denoms_h``.
    288        //
    289        // Note: These are essentially the m_i values in the RefreshDeriveBatch
    290        // function.
    291        coin_evs: CoinEnvelope[kappa][];
    292 
    293        // Signature by the old `coin <coin-priv>` over `TALER_RefreshMeltCoinAffirmationPS`.
    294        confirm_sig: CoinSignature;
    295 
    296     }
    297 
    298 
    299 TODO: definition of ``CoinSignature``
    300 
    301 .. ts:def:: CoinSignature
    302 
    303    // TODO: this needs to be fully expanded into a new interface
    304    type CoinSignature = string;
    305 
    306 
    307 TODO: explain /reveal-melt endpoint.
    308 
    309 .. ts:def:: NewMeltRevealRequest
    310 
    311    interface NewMeltRevealRequest {
    312       // The refresh commitment corresponding to the previous call to /melt
    313       // This is the Hash over:
    314       //   1. refresh_seed
    315       //   2. hash of all pairs of R-values, if applicable, skip otherwise
    316       //   3. denominations in order
    317       //   4. amount_with_fee
    318       //   5. kappa*n blinded planchet hashes (which include denomination information),
    319       //      depths first: [0..n)[0..n)[0..n)
    320       rc: RefreshCommitmentHash;
    321 
    322       // The disclosed kappaκ-1 signatures by the old coin's private key,
    323       // over Hash1a("Refresh", Cp, r, i), where Cp is the melted coin's public key,
    324       // r is the public refresh nonce from the metling step and i runs over the
    325       // _disclosed_ kappaκ-1 indices.
    326       signatures: CoinSignature[kappa-1];
    327 
    328       // IFF the denomination of the old coin had support for age restriction,
    329       // the client MUST provide the original age commitment, i. e. the
    330       // vector of public keys, or omitted otherwise.
    331       // The size of the vector MUST be the number of age groups as defined by the
    332       // Exchange in the field ``.age_groups`` of the extension ``age_restriction``.
    333       age_commitment?: Edx25519PublicKey[];
    334 
    335       // NOTE: THIS FIELD IS WORK-IN-PROGRESS
    336       // The old coin signs H(rc, noreveal_index).  This value MUST be stored by the
    337       // exchange and provided as part of the coin's history.  That way, the wallet
    338       // can be sure that the exchange hasn't altered the nonreveal_index when the
    339       // wallet does an idempotent melt request.
    340       noreveal_sig: CoinSignature;
    341    }
    342 
    343 Security Analysis
    344 =================
    345 
    346 TODO
    347 
    348 Drawbacks
    349 =========
    350 
    351 TODO
    352 
    353 Migration Strategy
    354 ==================
    355 
    356 TODO
    357 
    358 Discussion/Q&A
    359 ==============
    360 
    361 TODO
    362 
    363 
    364 References
    365 ==========
    366 .. [1] Work by Jonathan Levin et al., TU Eindhoven. Reference be added here once published