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