draft-guetschow-taler-protocol.md (52906B)
1 --- 2 v: 3 3 4 title: "The GNU Taler Protocol" 5 docname: draft-guetschow-taler-protocol 6 category: info 7 8 ipr: trust200902 9 workgroup: independent 10 stream: independent 11 keyword: 12 - taler 13 - cryptography 14 - ecash 15 - payments 16 17 #venue: 18 # repo: https://git.gnunet.org/lsd0009.git/ 19 # latest: https://lsd.gnunet.org/lsd0009/ 20 21 author: 22 - 23 name: Mikolai Gütschow 24 org: TUD Dresden University of Technology 25 abbrev: TU Dresden 26 street: Helmholtzstr. 10 27 city: Dresden 28 code: D-01069 29 country: Germany 30 email: mikolai.guetschow@tu-dresden.de 31 32 normative: 33 RFC20: 34 RFC2104: 35 RFC5869: 36 RFC6234: 37 RFC7748: 38 RFC8032: 39 RFC8785: 40 HKDF: DOI.10.1007/978-3-642-14623-7_34 41 SHS: DOI.10.6028/NIST.FIPS.180-4 42 43 informative: 44 45 46 --- abstract 47 48 \[ TBW \] 49 50 --- middle 51 52 # Introduction 53 54 \[ TBW \] 55 56 Beware that this document is still work-in-progress and may contain errors. 57 Use at your own risk! 58 59 # Notation 60 61 - `"abc"` denotes the literal string `abc` encoded as ASCII [RFC20], without trailing '\0' character. 62 - `a | b` denotes the concatenation of a with b 63 - `len(a)` denotes the length in bytes of the byte string a 64 - `padZero(y, a)` denotes the byte string a, zero-padded to the length of y bytes 65 - `bits(x)`/`bytes(x)` denotes the minimal number of bits/bytes necessary to represent the multiple precision integer x 66 - `uint(y, x)` denotes the `y` least significant bits of the integer `x`, zero-padded and encoded in network byte order (big endian) 67 - `uintY(x)` where `Y` is a positive integer number is equivalent to `uint(Y, x)` 68 - `random(y)` denotes a randomly generated sequence of y bits 69 - `a * b (mod N)` / `a ** b (mod N)` denotes the multiplication / exponentiation of multiple precision integers a and b, modulo N 70 - `for`, `if`, variable assignment `=`, and conditional operators are to be interpreted like their Python/Julia equivalents 71 - `data.key` denotes the property `key` on the object `data` 72 - `0..n` denotes the exclusive range of integer numbers from `0` to `n`, i.e., `0, 1, 2, ..., n-1` 73 - `⟨dataᵢ⟩` within a context of `i = 0..n` denotes `n` objects `dataᵢ`, represented in memory as a continuous array 74 - `⟨dataᵢ.key⟩` within a context of `i = 0..n` denotes an array of the `n` properties `key` of all `n` objects `dataᵢ` 75 76 # Cryptographic Primitives 77 78 // todo: maybe change this description to something more similar to protocol functions (Julia-inspired syntax) 79 80 ## Cryptographic Hash Functions 81 82 ### SHA-256 {#sha256} 83 84 ~~~ 85 SHA-256(msg) -> hash 86 87 Input: 88 msg input message of length L < 2^61 octets 89 90 Output: 91 hash message digest of fixed length HashLen = 32 octets 92 ~~~ 93 94 `hash` is the output of SHA-256 as per Sections 4.1, 5.1, 6.1, and 6.2 of [RFC6234]. 95 96 ### SHA-512 {#sha512} 97 98 ~~~ 99 SHA-512(msg) -> hash 100 101 Input: 102 msg input message of length L < 2^125 octets 103 104 Output: 105 hash message digest of fixed length HashLen = 64 octets 106 ~~~ 107 108 `hash` is the output of SHA-512 as per Sections 4.2, 5.2, 6.3, and 6.4 of [RFC6234]. 109 110 ### SHA-512-256 (truncated SHA-512) {#sha512-trunc} 111 112 ~~~ 113 SHA-512-256(msg) -> hash 114 115 Input: 116 msg input message of length L < 2^125 octets 117 118 Output: 119 hash message digest of fixed length HashLen = 32 octets 120 ~~~ 121 122 The output `hash` corresponds to the first 32 octets of the output of SHA-512 defined in {{sha512}}: 123 124 ~~~ 125 temp = SHA-512(msg) 126 hash = temp[0:31] 127 ~~~ 128 129 Note that this operation differs from SHA-512/256 as defined in [SHS] in the initial hash value. 130 131 132 ## Message Authentication Codes 133 134 ### HMAC {#hmac} 135 136 ~~~ 137 HMAC-Hash(key, text) -> out 138 139 Option: 140 Hash cryptographic hash function with output length HashLen 141 142 Input: 143 key secret key of length at least HashLen 144 text input data of arbitary length 145 146 Output: 147 out output of length HashLen 148 ~~~ 149 150 `out` is calculated as defined in [RFC2104]. 151 152 153 ## Key Derivation Functions 154 155 ### HKDF {#hkdf} 156 157 The Hashed Key Derivation Function (HKDF) used in Taler is an instantiation of [RFC5869] 158 with two different hash functions for the Extract and Expand step as suggested in [HKDF]: 159 `HKDF-Extract` uses `HMAC-SHA512`, while `HKDF-Expand` uses `HMAC-SHA256` (cf. {{hmac}}). 160 161 ~~~ 162 HKDF(salt, IKM, info, L) -> OKM 163 164 Inputs: 165 salt optional salt value (a non-secret random value); 166 if not provided, it is set to a string of 64 zeros. 167 IKM input keying material 168 info optional context and application specific information 169 (can be a zero-length string) 170 L length of output keying material in octets 171 (<= 255*32 = 8160) 172 173 Output: 174 OKM output keying material (of L octets) 175 ~~~ 176 177 The output OKM is calculated as follows: 178 179 ~~~ 180 PRK = HKDF-Extract(salt, IKM) with Hash = SHA-512 (HashLen = 64) 181 OKM = HKDF-Expand(PRK, info, L) with Hash = SHA-256 (HashLen = 32) 182 ~~~ 183 184 ### HKDF-Mod 185 186 Based on the HKDF defined in {{hkdf}}, this function returns an OKM that is smaller than a given multiple precision integer N. 187 188 ~~~ 189 HKDF-Mod(N, salt, IKM, info) -> OKM 190 191 Inputs: 192 N multiple precision integer 193 salt optional salt value (a non-secret random value); 194 if not provided, it is set to a string of 64 zeros. 195 IKM input keying material 196 info optional context and application specific information 197 (can be a zero-length string) 198 199 Output: 200 OKM output keying material (smaller than N) 201 ~~~ 202 203 The final output `OKM` is determined deterministically based on a counter initialized at zero. 204 205 ~~~ 206 counter = 0 207 do until OKM < N: 208 x = HKDF(salt, IKM, info | uint16(counter), bytes(N)) 209 OKM = uint(bits(N), x) 210 counter += 1 211 ~~~ 212 213 ## Non-Blind Signatures 214 215 ### Ed25519 {#ed25519} 216 217 Taler uses EdDSA instantiated with curve25519 as Ed25519, 218 as defined in Section 5.1 of [RFC8032]. 219 In particular, Taler does _not_ make use of Ed25519ph or Ed25519ctx 220 as defined in that document. 221 222 #### Key generation 223 224 ~~~ 225 Ed25519-GetPub(priv) -> pub 226 227 Input: 228 priv private Ed25519 key 229 230 Output: 231 pub public Ed25519 key 232 ~~~ 233 234 `pub` is calculated as described in Section 5.1.5 of [RFC8032]. 235 236 ~~~ 237 Ed25519-Keygen() -> (priv, pub) 238 239 Output: 240 priv private Ed25519 key 241 pub public Ed25519 key 242 ~~~ 243 244 `priv` and `pub` are calculated as described in Section 5.1.5 of [RFC8032], 245 which is equivalent to the following: 246 247 ~~~ 248 priv = random(256) 249 pub = Ed25519-GetPub(priv) 250 ~~~ 251 252 #### Signing 253 254 ~~~ 255 Ed25519-Sign(priv, msg) -> sig 256 257 Inputs: 258 priv Ed25519 private key 259 msg message to be signed 260 261 Output: 262 sig signature on the message by the given private key 263 ~~~ 264 265 `sig` is calculated as described in Section 5.1.6 of [RFC8032]. 266 267 #### Verifying 268 269 ~~~ 270 Ed25519-Verify(pub, msg, sig) -> out 271 272 Inputs: 273 pub Ed25519 public key 274 msg signed message 275 sig signature on msg 276 277 Output: 278 out true, if sig is a valid signature for msg 279 ~~~ 280 281 `out` is the outcome of the last check of Section 5.1.7 of [RFC8032]. 282 283 ## Key Agreement 284 285 ### X25519 286 287 Taler uses Elliptic Curve Diffie-Hellman (ECDH) on curve25519 as defined in Section 6.1 of [RFC7748], 288 but reuses Ed25519 keypairs for one side of the agreement instead of random bytes. 289 Depending on whether the private or public part is from Ed25519, two different functions are used. 290 291 {::comment} 292 see https://libsodium.gitbook.io/doc/advanced/scalar_multiplication 293 see https://libsodium.gitbook.io/doc/advanced/ed25519-curve25519 294 {:/} 295 296 ~~~ 297 ECDH-Ed25519-Priv(priv, pub) -> shared 298 299 Input: 300 priv private Ed25519 key 301 pub public X25519 key 302 303 Output: 304 shared shared secret based on the given keys 305 ~~~ 306 307 `shared` is calculated as follows, using the function X25519 defined in Section 5 of [RFC7748]: 308 309 ~~~ 310 priv' = SHA-512-256(priv) 311 // todo: missing bit clamping from https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L71 312 shared' = X25519(priv', pub) 313 shared = SHA-512(shared') 314 ~~~ 315 316 {::comment} 317 see GNUNET_CRYPTO_eddsa_ecdh 318 {:/} 319 320 ~~~ 321 ECDH-Ed25519-Pub(priv, pub) -> shared 322 323 Input: 324 priv private X25519 key 325 pub public Ed25519 key 326 327 Output: 328 shared shared secret based on the given keys 329 ~~~ 330 331 `shared` is calculated as follows, using the function X25519 defined in Section 5 of [RFC7748], 332 and `Convert-Point-Ed25519-Curve25519(p)` which implements the birational map of Section 4.1 of [RFC7748]: 333 334 ~~~ 335 pub' = Convert-Point-Ed25519-Curve25519(pub) 336 shared' = X25519(priv, pub') 337 shared = SHA-512(shared') 338 ~~~ 339 340 {::comment} 341 see GNUNET_CRYPTO_eddsa_ecdh 342 {:/} 343 344 ~~~ 345 ECDH-GetPub(priv) -> pub 346 347 Input: 348 priv private X25519 key 349 350 Output: 351 pub public X25519 key 352 ~~~ 353 354 `pub` is calculated according to Section 6.1 of [RFC7748]: 355 356 ~~~ 357 pub = X25519(priv, 9) 358 ~~~ 359 360 {::comment} 361 see GNUNET_CRYPTO_ecdhe_key_get_public 362 {:/} 363 364 ## Blind Signatures 365 366 ### RSA-FDH {#rsa-fdh} 367 368 #### Supporting Functions 369 370 ~~~ 371 RSA-FDH(msg, pubkey) -> fdh 372 373 Inputs: 374 msg message 375 pubkey RSA public key consisting of modulus N and public exponent e 376 377 Output: 378 fdh full-domain hash of msg over pubkey.N 379 ~~~ 380 381 `fdh` is calculated based on HKDF-Mod from {{hkdf-mod}} as follows: 382 383 ~~~ 384 info = "RSA-FDA FTpsW!" 385 salt = uint16(bytes(pubkey.N)) | uint16(bytes(pubkey.e)) 386 | pubkey.N | pubkey.e 387 fdh = HKDF-Mod(pubkey.N, salt, msg, info) 388 ~~~ 389 390 The resulting `fdh` can be used to test against a malicious RSA pubkey 391 by verifying that the greatest common denominator (gcd) of `fdh` and `pubkey.N` is 1. 392 393 ~~~ 394 RSA-FDH-Derive(bks, pubkey) -> out 395 396 Inputs: 397 bks blinding key secret of length L = 32 octets 398 pubkey RSA public key consisting of modulus N and public exponent e 399 400 Output: 401 out full-domain hash of bks over pubkey.N 402 ~~~ 403 404 `out` is calculated based on HKDF-Mod from {{hkdf-mod}} as follows: 405 406 ~~~ 407 info = "Blinding KDF" 408 salt = "Blinding KDF extractor HMAC key" 409 fdh = HKDF-Mod(pubkey.N, salt, bks, info) 410 ~~~ 411 412 #### Blinding 413 414 ~~~ 415 RSA-FDH-Blind(msg, bks, pubkey) -> out 416 417 Inputs: 418 msg message 419 bks blinding key secret of length L = 32 octets 420 pubkey RSA public key consisting of modulus N and public exponent e 421 422 Output: 423 out message blinded for pubkey 424 ~~~ 425 426 `out` is calculated based on RSA-FDH from {{rsa-fdh}} as follows: 427 428 ~~~ 429 data = RSA-FDH(msg, pubkey) 430 r = RSA-FDH-Derive(bks, pubkey) 431 r_e = r ** pubkey.e (mod pubkey.N) 432 out = r_e * data (mod pubkey.N) 433 ~~~ 434 435 #### Signing 436 437 ~~~ 438 RSA-FDH-Sign(data, privkey) -> sig 439 440 Inputs: 441 data data to be signed, an integer smaller than privkey.N 442 privkey RSA private key consisting of modulus N and private exponent d 443 444 Output: 445 sig signature on data by privkey 446 ~~~ 447 448 `sig` is calculated as follows: 449 450 ~~~ 451 sig = data ** privkey.d (mod privkey.N) 452 ~~~ 453 454 #### Unblinding 455 456 ~~~ 457 RSA-FDH-Unblind(sig, bks, pubkey) -> out 458 459 Inputs: 460 sig blind signature 461 bks blinding key secret of length L = 32 octets 462 pubkey RSA public key consisting of modulus N and public exponent e 463 464 Output: 465 out unblinded signature 466 ~~~ 467 468 `out` is calculated as follows: 469 470 ~~~ 471 r = RSA-FDH-Derive(bks, pubkey) 472 r_inv = inverse of r (mod pubkey.N) 473 out = sig * r_inv (mod pubkey.N) 474 ~~~ 475 476 #### Verifying 477 478 ~~~ 479 RSA-FDH-Verify(msg, sig, pubkey) -> out 480 481 Inputs: 482 msg message 483 sig signature of pubkey over msg 484 pubkey RSA public key consisting of modulus N and public exponent e 485 486 Output: 487 out true, if sig is a valid signature 488 ~~~ 489 490 `out` is calculated based on RSA-FDH from {{rsa-fdh}} as follows: 491 492 ~~~ 493 data = RSA-FDH(msg, pubkey) 494 exp = sig ** pubkey.e (mod pubkey.N) 495 out = (data == exp) 496 ~~~ 497 498 ### Clause-Schnorr {#cbs} 499 500 # Datatypes and Notation 501 502 ## Amounts {#amounts} 503 504 Amounts are represented in Taler as positive fixed-point values 505 consisting of `value` as the non-negative integer part of the base currency, 506 the `fraction` given in units of one hundred millionth (1e-8) of the base currency, 507 and `currency` as the 3-11 ASCII characters identifying the currency. 508 509 Whenever used in the protocol, the binary representation of an `amount` is 510 `uint64(amount.value) | uint32(amount.fraction) | padZero(12, amount.currency)`. 511 512 ## Timestamps 513 514 Absolute timestamps are represented as `uint64(x)` where `x` corresponds to 515 the microseconds since `1970-01-01 00:00 CEST` (the UNIX epoch). 516 The special value `0xFFFFFFFFFFFFFFFF` represents "never". 517 <!-- 518 // todo: check if needed and correct 519 Relative timestamps are represented as `uint64(x)` where `x` is given in microseconds. 520 The special value `0xFFFFFFFFFFFFFFFF` represents "forever". 521 --> 522 523 ## Signatures 524 525 All messages to be signed in Taler start with a header containing their total size 526 (including the header) and a fixed signing context (purpose) as registered by GANA in the 527 [GNUnet Signature Purposes](https://gana.gnunet.org/gnunet-signatures/gnunet_signatures.html) 528 registry. Taler-specific purposes start at 1000. 529 530 ~~~ 531 Gen-Msg(purpose, msg) -> out 532 533 Inputs: 534 purpose signature purpose as registered at GANA 535 msg message content (excl. header) to be signed 536 537 Output: 538 out complete message (incl. header) to be signed 539 ~~~ 540 541 `out` is formed as follows: 542 543 ~~~ 544 out = uint32(len(msg) + 8) | uint32(purpose) | msg 545 ~~~ 546 547 ## Helper Functions 548 549 There are a certain number of single-argument functions which are often needed, 550 and therefore omit the parentheses of the typical function syntax: 551 552 - `Knows data` specifies `data` that is known a priori at the start of the protocol operation 553 - `Determine data` specifies `data` that is determined according to the business logic and current state of the protocol entity 554 - `Check cond` verifies that the boolean condition or variable `cond` is true, 555 or aborts the protocol operation otherwise 556 - `Persist data` persists the given `data` to the local database 557 - `data = Lookup by key` retrieves previously persisted `data` by the given `key` 558 - `Sum ⟨dataᵢ⟩` is valid for numerical objects `dataᵢ` including amounts (cf. {{amounts}}), 559 and denotes the numerical sum of these objects 560 561 Some more functions that are commonly used throughout {{protocol}}: 562 563 ~~~ 564 Hash-Denom(denom) = 565 SHA-512(uint32(0) | uint32(1) | denom.pub) 566 567 Hash-Planchet(planchet, denom) = 568 SHA-512( SHA-512( denom.pub ) | uint32(0x1) | planchet ) 569 570 Hash-Contract(contract) = 571 SHA-512( canonicalJSON(contract) | 0x0) 572 573 Check-Subtract(value, subtrahend) = 574 Check value >= subtrahend 575 Persist value -= subtrahend 576 ~~~ 577 578 `canonicalJSON(data)` canonicalizes `data` represented as JSON 579 according to the JSON Canonicalization Scheme (JCS) defined in [RFC8785]. 580 Note that `data` as input to `canonicalJSON` is restricted as follows for Taler: 581 582 - For JSON Object member names, only strings matching the regular expression `^[0-9A-Z_a-z]+$` or the literal names `$forgettable` or `$forgotten` are allowed. 583 This makes the sorting of object members easier, as [RFC8785] requires sorting by UTF-16 code points. 584 - Floating point numbers are forbidden. Numbers must be integers in the range `-(2**53 - 1)` to `(2**52) - 1`. 585 586 # The Taler Crypto Protocol {#protocol} 587 588 The Taler payment protocol is a token-based _e-cash_ system 589 which ensures anonymity for payers (much like physical cash), 590 while guaranteing income transparency on the payees' side (much like most digital payment systems). 591 Contrary to what the name might suggest, 592 Taler neither is a separate currency (as cryptocurrencies do) 593 nor is it tied to a specific currency. 594 Instead, the payment system operator offering the Taler payment protocol 595 can freely choose the assets backing the payment system. 596 597 The basic system consists of three types of entities: 598 599 1. The Taler _exchange_ is run by the payment system operator. 600 It is the central, trusted entity which hands out e-cash and holds the corresponding value. 601 2. A Taler _wallet_ manages e-cash in self-custody for end users. 602 3. A Taler _merchant_ can redeem e-cash at the exchange 603 after the wallet authorized a deposit permission during a payment. 604 605 E-cash in Taler is represented as digital tokens called _coins_. 606 They are public-private keypairs where ownership of the coin 607 is equivalent to the knowledge of the private key `coin.priv`. 608 Every coin has an initial value corresponding to a denomination (`denom`) offered by the exchange. 609 The validity of coins is signaled by the presence of 610 a valid denomination signature `coin.sig` on the (hash of the) public key `coin.pub`. 611 To ensure payer anonymity, the exchange generates `coin.sig` without learning the actual (hash of) `coin.pub` 612 using a _blind_ signature scheme. 613 614 Wallets obtain coins from the exchange during _withdrawal_ (cf. {{withdraw}}) 615 and use them during _payment_ at merchants, who in turn _deposit_ them at the exchange (cf. {{payment}}). 616 Residual value on partly spent coins can be _refreshed_ by the wallet subsequently in order to obtain unlinkable change (cf. {{refresh}}). 617 Taler also supports receiving e-cash in a wallet without acting as a merchant using _wallet-to-wallet payments_ (W2W, cf. {{w2w}}), 618 which are always handled via the exchange. 619 620 Honest operation of the exchange can be optionally supervised by an independant third-party Taler _auditor_. 621 This supervision is not part of the basic Taler protocol and thus not part of this document. 622 623 ~~~ 624 - exchange - 625 / \ 626 Withdrawal / \ Deposit 627 Refresh / W2W \ 628 / \ 629 wallet ----------- merchant 630 Payment 631 ~~~ 632 633 // todo: capitalize wallet, exchange, merchant everywhere? 634 635 In the default configuration, Taler uses RSA-FDH (cf. {{rsa-fdh}}) for (blind) denomination signatures 636 and Ed25519 (cf. {{ed25519}}) signatures everywhere else. 637 Clause-Schnorr Signatures (cf. {{cbs}}) provide an alternative blind signature scheme operating on Elliptic Curves. 638 As their usage is still experimental, they are not described as part of this document. 639 640 Taler has optional support for age-restricted coins, enabling privacy-preserving age restriction. 641 As an optional feature, it is not part of the basic Taler protocol and thus left out of the description in this document. 642 643 ## Obtaining E-Cash 644 645 ### Withdrawal {#withdraw} 646 647 The wallet generates `n > 0` coins `⟨coinᵢ⟩` and requests `n` signatures `⟨blind_sigᵢ⟩` from the exchange, 648 attributing value to the coins according to `n` chosen denominations `⟨denomᵢ⟩`. 649 The total value and withdrawal fee (defined by the exchange per denomination) 650 must be smaller or equal to the amount stored in the single reserve used for withdrawal. 651 652 // todo: document TALER_MAX_COINS = 64 per operation (due to CS-encoding) 653 654 // todo: extend with extra roundtrip for CBS 655 656 ~~~ 657 wallet exchange 658 Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ 659 | | 660 +-----------------------------+ | 661 | (W1) reserve key generation | | 662 +-----------------------------+ | 663 | | 664 |----------- (bank transfer) ----------->| 665 | (subject: reserve.pub, amount: value) | 666 | | 667 | +------------------------------+ 668 | | Persist (reserve.pub, value) | 669 | +------------------------------+ 670 | | 671 +-----------------------------------+ | 672 | (W2) coin generation and blinding | | 673 +-----------------------------------+ | 674 | | 675 |-------------- /withdraw -------------->| 676 | (reserve.pub, planchets, sig) | 677 | | 678 | +--------------------------------+ 679 | | (E1) coin issuance and signing | 680 | +--------------------------------+ 681 | | 682 |<---------- (⟨blind_sigᵢ⟩) -------------| 683 | | 684 +----------------------+ | 685 | (W3) coin unblinding | | 686 +----------------------+ | 687 | | 688 ~~~ 689 690 where (for RSA, without age-restriction) 691 692 ~~~ pseudocode 693 (W1) reserve key generation (wallet) 694 695 reserve = Ed25519-Keygen() 696 Persist (reserve, value) 697 ~~~ 698 699 The wallet derives coins and blinding secrets using a HKDF from a single seed per withdrawal operation, 700 together with an integer index. 701 This is strictly speaking an implementation detail since the seed is never revealed to any other party, 702 and might be chosen to be implemented differently. 703 704 ~~~ pseudocode 705 (W2) coin generation and blinding (wallet) 706 707 batch_seed = random(256) 708 Persist batch_seed 709 for i in 0..n: 710 coin_seedᵢ = HKDF(uint32(i), batch_seed, "taler-withdrawal-coin-derivation", 64) 711 blind_secretᵢ = coin_seedᵢ[32:] 712 coinᵢ.priv = coin_seedᵢ[:32] 713 coinᵢ.pub = Ed25519-GetPub(coinᵢ.priv) 714 h_denomᵢ = Hash-Denom(denomᵢ) 715 planchetᵢ = RSA-FDH-Blind(SHA-512(coinᵢ.pub), blind_secretᵢ, denomᵢ.pub) 716 h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) 717 planchets = (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) 718 msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, 719 ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ 720 | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) 721 sig = Ed25519-Sign(reserve.priv, msg) 722 723 // todo: exchange.git uses different derivation than wallet-core.git (above): 724 ⟨coin_seedᵢ⟩ = HKDF(uint32(n), batch_seed, "taler-withdraw-secrets", 32*n) 725 for i in 0..n: 726 blind_secretᵢ = HKDF("bks", coin_seedᵢ, "", 32) 727 coinᵢ.priv = HKDF("coin", coin_seedᵢ, "", 32) 728 ~~~ 729 730 ~~~ pseudocode 731 (E1) coin issuance and signing (exchange) 732 733 (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) = planchets 734 for i in 0..n: 735 denomᵢ = Lookup by h_denomᵢ 736 Check denomᵢ known and not withdraw-expired 737 h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) 738 msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, 739 ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ 740 | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) 741 Check Ed25519-Verify(reserve.pub, msg, sig) 742 Check reserve KYC status ok or not needed 743 total = Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 744 Check-Subtract(reserve.balance, total) 745 for i in 0..n: 746 blind_sigᵢ = RSA-FDH-Sign(planchetᵢ, denomᵢ.priv) 747 Persist withdrawal // todo: what exactly? should be checked first for replay? 748 ~~~ 749 750 ~~~ pseudocode 751 (W3) coin unblinding (wallet) 752 753 for i in 0..n: 754 coinᵢ.sig = RSA-FDH-Unblind(blind_sigᵢ, blind_secretᵢ, denomᵢ.pub) 755 Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) 756 coinᵢ.value = denomᵢ.value 757 coinᵢ.h_denom = h_denomᵢ 758 coinᵢ.blind_secret = blind_secretᵢ // todo: why save blind_secret, if batch_seed already persisted? 759 Persist ⟨coinᵢ⟩ 760 ~~~ 761 762 ### Recoup {#withdraw-recoup} 763 764 // todo 765 766 ## Payment with E-Cash 767 768 ### Payment and Deposit {#payment} 769 770 The wallet obtains `contract` information for an `order` from the merchant 771 after claiming it with a `nonce`. 772 Payment of the order is prepared by signing (partial) deposit authorizations `⟨depositᵢ⟩` with coins `⟨coinᵢ⟩` of certain denominations `⟨denomᵢ⟩`, 773 where the sum of all contributions (`contributionᵢ + denomᵢ.fee_deposit <= denomᵢ.value`) 774 must match the `contract.price` plus potential deposit fees `⟨denomᵢ.fee_deposit⟩`. 775 The payment is complete as soon as the merchant successfully redeems the deposit authorizations at the exchange. 776 777 Deposit could also be used directly by a wallet with its own payto and a minimal contract. 778 779 // todo: should we integrate payment templates here? 780 781 ~~~ 782 wallet merchant exchange 783 Knows ⟨coinᵢ⟩ Knows merchant.priv Knows exchange.priv 784 | Knows exchange, payto Knows ⟨denomᵢ⟩ 785 | | | 786 | +-----------------------+ | 787 | | (M1) order generation | | 788 | +-----------------------+ | 789 | | | 790 |<--- (QR-Code / NFC / URI) ---| | 791 | (order.{id,token?}) | | 792 | | | 793 +-----------------------+ | | 794 | (W1) nonce generation | | | 795 +-----------------------+ | | 796 | | | 797 |-- /orders/{order.id}/claim ->| | 798 | (nonce.pub, order.token?) | | 799 | | | 800 | +--------------------------+ | 801 | | (M2) contract generation | | 802 | +--------------------------+ | 803 | | | 804 |<-- (contract, merchant.pub, -| | 805 | sig) | | 806 | | | 807 +--------------------------+ | | 808 | (W2) payment preparation | | | 809 +--------------------------+ | | 810 | | | 811 |--- /orders/{order.id}/pay -->| | 812 | (⟨depositᵢ⟩) | | 813 | | | 814 | +--------------------------+ | 815 | | (M3) deposit preparation | | 816 | +--------------------------+ | 817 | | | 818 | |-------- /batch-deposit ----->| 819 | | (info, h_contract, ⟨depositᵢ⟩| 820 | | merchant.pub, sig) | 821 | | | 822 | | +--------------------+ 823 | | | (E1) deposit check | 824 | | +--------------------+ 825 | | | 826 | |<------ (time_deposit, -------| 827 | | exchange.pub, sig) | 828 | | | 829 | +---------------------------+ | 830 | | (M4) deposit verification | | 831 | +---------------------------+ | 832 | | | 833 |<----------- (sig) -----------| | 834 | | | 835 +---------------------------+ | | 836 | (W3) payment verification | | | 837 +---------------------------+ | | 838 | | | 839 ~~~ 840 841 where (without age restriction, policy and wallet data hash) 842 843 ~~~ pseudocode 844 (M1) order generation (merchant) 845 846 wire_salt = random(128) 847 Determine price, and ASCII strings id, info, token? 848 Persist order = (id, price, info, token?, wire_salt) 849 ~~~ 850 851 ~~~ pseudocode 852 (W1) nonce generation (wallet) 853 854 nonce = Ed25519-Keygen() 855 Persist nonce.priv 856 ~~~ 857 858 Note that the private key of `nonce` is currently not used anywhere in the protocol. 859 However, it could be used in the future to prove ownership of an order transaction, 860 enabling use-cases such as "unclaiming" or transferring an order to another person, 861 or proving the payment without resorting to the individual coins. 862 863 ~~~ pseudocode 864 (M2) contract generation (merchant) 865 866 Check order.token? == token? 867 h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) 868 timestamp = now() 869 Determine refund_deadline, wire_deadline from timestamp 870 Determine max_fees from price 871 contract = (order.{id,price,info,token?}, exchange, h_wire, timestamp, refund_deadline, wire_deadline, max_fees) 872 contract.nonce = nonce.pub 873 Persist contract 874 h_contract = Hash-Contract(contract) 875 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 876 sig = Ed25519-Sign(merchant.priv, msg) 877 ~~~ 878 879 ~~~ pseudocode 880 (W2) payment preparation (wallet) 881 882 h_contract = Hash-Contract(contract) 883 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 884 Check Ed25519-Verify(merchant.pub, msg, sig) 885 Check contract.nonce == nonce 886 // TODO: double-check extra hash check? 887 for i in 0..n: 888 Determine coinᵢ, denomᵢ, contribution_netᵢ for contract.{exchange,price,max_fees} 889 contribution_grossᵢ = contribution_netᵢ + denomᵢ.fee_deposit 890 Check-Subtract(coinᵢ.value, contribution_grossᵢ) 891 msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, 892 ( h_contract | uint256(0x0) 893 | uint512(0x0) | contract.h_wire | coinᵢ.h_denom 894 | timestamp | contract.refund_deadline 895 | contribution_grossᵢ | denomᵢ.fee_deposit 896 | merchant.pub | uint512(0x0) )) 897 sigᵢ = Ed25519-Sign(coinᵢ.priv, msgᵢ) 898 depositᵢ = (coinᵢ.{pub,sig,h_denom}, contribution_grossᵢ, sigᵢ) 899 Persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) 900 ~~~ 901 902 ~~~ pseudocode 903 (M3) deposit preparation (merchant) 904 905 for i in 0..n: 906 denomᵢ = Lookup by depositᵢ.coin.h_denom 907 contribution_netᵢ = depositᵢ.contribution_gross - denomᵢ.fee_deposit 908 Check Sum ⟨contribution_netᵢ⟩ >= contract.price - contract.max_fees 909 info.time = contract.{timestamp, wire_deadline, refund_deadline} 910 info.wire = (payto, wire_salt) 911 h_contract = Hash-Contract(contract) 912 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 913 sig = Ed25519-Sign(merchant.priv, msg) 914 ~~~ 915 916 TODO: what about wire_fees, those should be checked for as well, or do we just assume merchant will pay those? 917 see src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c:2760 918 919 ~~~ pseudocode 920 (E1) deposit check (exchange) 921 922 h_wire = HKDF(info.wire.wire_salt, info.wire.payto, "merchant-wire-signature", 64) 923 for i in 0..n: 924 coinᵢ = depositᵢ.coin 925 denomᵢ = Lookup by coinᵢ.h_denom 926 Check denomᵢ known and not deposit-expired 927 msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, 928 ( h_contract | uint256(0x0) 929 | uint512(0x0) | h_wire | coinᵢ.h_denom 930 | info.time.timestamp | info.time.refund_deadline 931 | depositᵢ.contribution_gross | denomᵢ.fee_deposit 932 | merchant.pub | uint512(0x0) )) 933 Check Ed25519-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) 934 Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) 935 Check-Subtract(coinᵢ.value, depositᵢ.contribution_gross) 936 Persist deposit-record 937 schedule bank transfer to payto 938 time_deposit = now() 939 msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, 940 ( h_contract | h_wire | uint512(0x0) 941 | time_deposit | info.time.wire_deadline 942 | info.time.refund_deadline 943 | Sum ⟨depositᵢ.contribution_gross⟩ 944 | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) 945 sig = Ed25519-Sign(exchange.priv, msg) 946 ~~~ 947 948 ~~~ pseudocode 949 (M2) deposit verification (merchant) 950 951 h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) 952 msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, 953 ( h_contract | h_wire | uint512(0x0) 954 | time_deposit | contract.wire_deadline 955 | contract.refund_deadline 956 | Sum ⟨depositᵢ.contribution⟩ 957 | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) 958 Check Ed25519-Verify(exchange.pub, msg, sig) 959 msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) 960 sig = Ed25519-Sign(merchant.priv, msg) 961 ~~~~ 962 963 ~~~ pseudocode 964 (W3) payment verification (wallet) 965 966 msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) 967 Check Ed25519-Verify(merchant.pub, msg, sig) 968 ~~~ 969 970 ### Refund {#refund} 971 972 A wallet can request a refund for an order from the merchant after it has been completed successfully 973 (cf. {{payment}}) and before the merchant has been paid out by the exchange (i.e., before `contract.wire_deadline`). 974 The merchant needs to approve the refund via its business logic, 975 and is free to decide the total amount of the refund 976 as well as which coins' deposit operations are (potentially partly) invalidated. 977 After the exchange has accepted the refund request, 978 the coins obtain their (partial) value back. 979 The wallet should proceed to refresh (cf. {{refresh}}) the coins before spending them again 980 to obtain unlinkability. 981 982 In case the wallet itself has used deposit to its own payto, 983 it can act as the merchant in the protocol below. 984 985 ~~~ 986 wallet merchant exchange 987 Knows order.id Knows merchant.priv Knows deposit_record 988 Knows contract | for coinᵢ.pub 989 | | | 990 +---------------------+ | | 991 | (W1) refund request | | | 992 +---------------------+ | | 993 | | | 994 |- /orders/{order.id}/refund ->| | 995 | (h_contract) | | 996 | | | 997 | +------------------------+ | 998 | | (M1) refund processing | | 999 | +------------------------+ | 1000 | | | 1001 | |- /coins/{coinᵢ.pub}/refund ->| 1002 | | (valueᵢ, h_contract, id, | 1003 | | merchant.pub, sigᵢ) | 1004 | | | 1005 | | +-------------------+ 1006 | | | (E1) refund check | 1007 | | +-------------------+ 1008 | | | 1009 | |<--- (exchange.pub, sigᵢ) ----| 1010 | | | 1011 | +--------------------------+ | 1012 | | (M2) refund confirmation | | 1013 | +--------------------------+ | 1014 | | | 1015 |<-----(value, ⟨refundᵢ⟩,------| | 1016 | merchant.pub) | | // todo: why merchant.pub if no sig transmitted? 1017 | | | 1018 +-----------------------+ | | 1019 | (W2) refund reception | | | 1020 +-----------------------+ | | 1021 | | | 1022 ~~~ 1023 1024 where (for RSA, without age-restriction) 1025 1026 {::comment} 1027 1028 ⟨ᵧₖᵢ⟩ 1029 {:/} 1030 1031 ~~~ pseudocode 1032 (W1) refund request (wallet) 1033 1034 h_contract = Hash-Contract(contract) 1035 ~~~ 1036 1037 {::comment} 1038 1039 ⟨ᵧₖᵢ⟩ 1040 {:/} 1041 1042 ~~~ pseudocode 1043 (M1) refund processing (merchant) 1044 1045 Check h_contract known and refund possible 1046 time = now() 1047 ⟨coinᵢ⟩ = Lookup by h_contract 1048 id = uint32(random(32)) 1049 for i in 0..n: 1050 denomᵢ = Lookup by coinᵢ.h_denom 1051 valueᵢ = refund amount // todo: split wisely 1052 msgᵢ = Gen-Msg(MERCHANT_REFUND, 1053 ( h_contract | coinᵢ.pub | id | valueᵢ | denomᵢ.fee_refund )) 1054 sigᵢ = Ed25519-Sign(merchant.priv, msgᵢ) 1055 ~~~ 1056 1057 {::comment} 1058 1059 ⟨ᵧₖᵢ⟩ 1060 {:/} 1061 1062 ~~~ pseudocode 1063 (E1) refund check and confirmation (exchange) 1064 1065 deposit_record = Lookup by h_contract // todo: needs to be persisted before with order.id and used coins! 1066 Check refund possible (prior to wire transfer deadline) 1067 for i in 0..n: 1068 Check coinᵢ.pub part of deposit_record 1069 denomᵢ = Lookup by coinᵢ.pub 1070 msgᵢ = Gen-Msg(MERCHANT_REFUND, 1071 ( h_contract | coinᵢ.pub | id | valueᵢ | denomᵢ.fee_refund )) 1072 Check Ed25519-Verify(merchant.pub, msgᵢ, sigᵢ) 1073 Check valueᵢ >= denomᵢ.fee_refund 1074 remove/update scheduled wire transfer 1075 Persist coinᵢ.value += valueᵢ - denomᵢ.fee_refund 1076 msgᵢ = Gen-Msg(MERCHANT_REFUND_OK, SHA-512(order.id)) 1077 sigᵢ = Ed25519-Sign(exchange.priv, msgᵢ) 1078 ~~~ 1079 1080 {::comment} 1081 1082 ⟨ᵧₖᵢ⟩ 1083 {:/} 1084 1085 ~~~ pseudocode 1086 (M2) refund confirmation (merchant) 1087 1088 for i in 0..n: 1089 msgᵢ = Gen-Msg(MERCHANT_REFUND_OK, SHA-512(order.id)) 1090 Check Ed25519-Verify(exchange.pub, msgᵢ, sigᵢ) 1091 update business logic 1092 refundᵢ = (valueᵢ, sigᵢ, id, coinᵢ.pub, time) 1093 value = sum ⟨valueᵢ⟩ 1094 ~~~ 1095 1096 {::comment} 1097 1098 ⟨ᵧₖᵢ⟩ 1099 {:/} 1100 1101 ~~~ pseudocode 1102 (W2) refund reception (wallet) 1103 1104 for i in 0..n: 1105 (valueᵢ, sigᵢ, id, coinᵢ.pub, time) = refundᵢ 1106 update persistent transaction information 1107 refresh ⟨coinᵢ⟩ 1108 ~~~ 1109 1110 ## Obtaining unlinkable change 1111 1112 ### Refresh {#refresh} 1113 1114 The wallet obtains `n` new coins `⟨coinᵢ⟩` of denominations `⟨denomᵢ⟩` 1115 in exchange for one old `coin` of denomination `denom` from the exchange. 1116 There are three reasons why a wallet needs to do this: 1117 1118 1. Obtaining unlinkable change after using only a part of the coin's value during a payment (cf. {{payment}}), 1119 i.e., where `coin.value < denom.value` 1120 2. Obtaining unlinkable change after a successful refund (cf. {{refund}}) 1121 3. Renewing a coin before it deposit-expires 1122 1123 The sum of the refresh fee of `denom` and the new denominations' values and withdrawal fees (defined by the exchange) 1124 must be smaller or equal to the residual value of the old coin (`coin.value`). 1125 1126 The private key of each new coin candidate `⟨coinₖᵢ.priv⟩` is transitively derived from the old coin's private key `coin.priv` 1127 via a 512-bit secret `⟨sharedₖᵢ⟩` according to `Refresh-Derive`. 1128 The secret is regeneratable with the knowledge of `coin.priv` via the link protocol (cf. {{link}}). 1129 The derivation ensures that ownership of coins (knowledge of the private key) is correctly transferred, 1130 and thereby that value transfer among untrusted parties can only happen via payment and deposit, not via refresh. 1131 1132 ~~~ 1133 Refresh-Derive(shared, i, denom) = 1134 planchet_seed = HKDF(uint32(i), shared, "taler-coin-derivation", 64) 1135 blind_secret = HKDF("bks", planchet_seed, "", 32) 1136 coin.priv = HKDF("coin", planchet_seed, "", 32) 1137 coin.pub = Ed25519-GetPub(coin.priv) 1138 planchet = RSA-FDH-Blind(SHA-512(coin.pub), blind_secret, denom.pub) 1139 h_planchet = Hash-Planchet(planchet, denom) 1140 return (coin, blind_secret, planchet, h_planchet) 1141 ~~~ 1142 1143 Taler uses a cut-and-choose protocol with the fixed parameter `κ=3` to enforce correct derivation 1144 of `⟨sharedₖᵢ⟩` from a single seed per batch of planchets `⟨batch_seedₖ⟩` 1145 (in (κ-1)/κ of the cases, making income concealment for tax evasion purposes unpractical). 1146 1147 Refreshing consists of two parts: 1148 1149 1. Melting of the old coin and commiting to κ batches of blinded planchet candidates 1150 2. Revelation of κ-1 secrets `⟨revealed_seedₖ⟩` to prove the proper construction of the (revealed) batches of blinded planchet candidates. 1151 1152 ~~~ 1153 wallet exchange 1154 Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ 1155 Knows coin | 1156 | | 1157 +-------------------+ | 1158 | (W1) coin melting | | 1159 +-------------------+ | 1160 | | 1161 |---------------- /melt ---------------->| 1162 | (coin.{pub,sig,h_denom}, value, | 1163 | refresh_seed, planchets, sig) | 1164 | | 1165 | +---------------------------------------+ 1166 | | (E1) gamma selection and coin signing | 1167 | +---------------------------------------+ 1168 | | 1169 |<------ (ɣ, exchange.pub, sig) ---------| 1170 | | 1171 +------------------------+ | 1172 | (W2) secret revelation | | 1173 +------------------------+ | 1174 | | 1175 |------------ /reveal-melt ------------->| 1176 | (commitment, ⟨revealed_seedₖ⟩) | 1177 | | 1178 | +----------------------------+ 1179 | | (E2) commitment validation | 1180 | +----------------------------+ 1181 | | 1182 |<---------- (⟨blind_sigᵢ⟩) -------------| 1183 | | 1184 +----------------------+ | 1185 | (W3) coin unblinding | | 1186 +----------------------+ | 1187 | | 1188 ~~~ 1189 1190 where (for RSA, without age-restriction) 1191 1192 {::comment} 1193 // see TALER_EXCHANGE_get_melt_data 1194 ⟨batch_seedₖ⟩ // see TALER_refresh_expand_seed_to_kappa_batch_seeds 1195 ⟨transferₖᵢ.priv⟩ // see TALER_refresh_expand_batch_seed_to_transfer_data 1196 h_planchetₖᵢ // see TALER_coin_ev_hash 1197 h_planchetsₖ // see TALER_wallet_blinded_planchet_details_hash 1198 commitment // see TALER_refresh_get_commitment 1199 1200 ⟨ᵧₖᵢ⟩ 1201 {:/} 1202 1203 ~~~ pseudocode 1204 (W1) coin melting (wallet) 1205 1206 refresh_seed = random(256) 1207 ⟨batch_seedₖ⟩ = HKDF("refresh-batch-seeds", refresh_seed, coin.priv, k*64) 1208 for k in 0..κ: 1209 ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) 1210 for i in 0..n: 1211 transferₖᵢ.pub = ECDH-GetPub(transferₖᵢ.priv) 1212 sharedₖᵢ = ECDH-Ed25519-Pub(transferₖᵢ.priv, coin.pub) 1213 (coinₖᵢ, blind_secretₖᵢ, planchetₖᵢ, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1214 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1215 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1216 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1217 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1218 for i in 0..n: 1219 h_denomᵢ = Hash-Denom(denomᵢ) 1220 planchets = (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) 1221 msg = Gen-Msg(WALLET_COIN_MELT, 1222 ( commitment | coin.h_denom | uint256(0x0) 1223 | value | denom.fee_refresh )) 1224 sig = Ed25519-Sign(coin.priv, msg) 1225 Persist (coin.denom.pub, ...) // todo: double-check 1226 ~~~ 1227 1228 {::comment} 1229 1230 see TEH_handler_melt 1231 1232 ⟨ᵧₖᵢ⟩ 1233 {:/} 1234 1235 ~~~ pseudocode 1236 (E1) gamma selection and coin signing (exchange) 1237 1238 denom = Lookup by coin.h_denom 1239 Check denom known and not deposit-expired 1240 Check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) 1241 Check coin.pub known and dirty 1242 (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) = planchets 1243 for i in 0..n: 1244 denomᵢ = Lookup by h_denomᵢ 1245 Check denomᵢ known and not withdraw-expired 1246 value' = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1247 Check value' == value 1248 Check-Subtract(coin.value, value) 1249 for k in 0..κ: 1250 for i in 0..n: 1251 h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) 1252 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1253 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1254 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1255 msg = Gen-Msg(WALLET_COIN_MELT, 1256 ( commitment | coin.h_denom | uint256(0x0) 1257 | value | denom.fee_refresh )) 1258 Check Ed25519-Verify(coin.pub, msg, sig) 1259 refresh_record = Lookup by commitment 1260 (ɣ, _, _, done, _) = refresh_record 1261 if refresh_record not found: 1262 ɣ = 0..κ at random 1263 for i in 0..n: 1264 blind_sigᵢ = RSA-FDH-Sign(planchetᵧᵢ, denomᵧᵢ.priv) 1265 link_info = (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) 1266 Persist refresh_record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) 1267 msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, 1268 ( commitment | uint32(ɣ) )) 1269 sig = Ed25519-Sign(exchange.priv, msg) 1270 ~~~ 1271 1272 {::comment} 1273 1274 // see src/lib/exchange_api_post-melt.c: handle_melt_finished 1275 // see src/lib/exchange_api_post-reveal-melt.c: perform_protocol 1276 1277 ⟨ᵧₖᵢ⟩ 1278 {:/} 1279 1280 ~~~ pseudocode 1281 (W2) secret revelation (wallet) 1282 1283 Check exchange.pub known 1284 msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, 1285 ( commitment | uint32(ɣ) )) 1286 Check Ed25519-Verify(exchange.pub, msg, sig) 1287 Persist refresh-challenge // what exactly? 1288 for k in 0..κ and k != ɣ: 1289 revealed_seedₖ = batch_seedₖ 1290 ~~~ 1291 1292 {::comment} 1293 1294 // see TEH_handler_reveal_melt 1295 1296 ⟨ᵧₖᵢ⟩ 1297 {:/} 1298 1299 ~~~ pseudocode 1300 (E2) commitment validation (exchange) 1301 1302 refresh_record = Lookup by commitment 1303 (ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = refresh_record 1304 Check not done // todo: sure? 1305 for k in 0..κ and k != ɣ: 1306 ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) 1307 for i in 0..n: 1308 transferₖᵢ.pub = ECDH-GetPub(transferₖᵢ.priv) 1309 sharedₖᵢ = ECDH-Ed25519-Pub(transferₖᵢ.priv, coin.pub) 1310 (_, _, _, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1311 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1312 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1313 commitment' = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1314 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1315 Check commitment == commitment' 1316 Persist refresh_record = (_, _, _, true, _) 1317 ~~~ 1318 1319 {::comment} 1320 1321 // see src/lib/exchange_api_post-reveal-melt.c: reveal_melt_ok 1322 1323 ⟨ᵧₖᵢ⟩ 1324 {:/} 1325 1326 ~~~ pseudocode 1327 (W3) coin unblinding (wallet) 1328 1329 for i in 0..n: 1330 coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) 1331 Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) 1332 coinᵧᵢ.h_denom = h_denomᵢ 1333 Persist ⟨coinᵧᵢ⟩ 1334 ~~~ 1335 1336 ### Link {#link} 1337 1338 Coins ⟨coinᵧᵢ⟩ obtained via the refresh protocol (cf. {{refresh}}) can be regenerated 1339 with the knowledge of the old coin's private key `coin.priv` using the link protocol, 1340 integrated in the coin history endpoint. 1341 1342 ~~~ 1343 wallet exchange 1344 Knows coin Knows refresh_record for coin.pub 1345 | | 1346 +----------------------+ | 1347 | (W1) history request | | 1348 +----------------------+ | 1349 | | 1350 |------ /coins/{coin.pub}/history ------>| 1351 | (sig) | 1352 | | 1353 | +----------------------------+ 1354 | | (E1) refresh secret lookup | 1355 | +----------------------------+ 1356 | | 1357 |<------------- (melt_info) -------------| 1358 | | 1359 +-----------------------+ | 1360 | (W2) coin acquisition | | 1361 +-----------------------+ | 1362 | | 1363 ~~~ 1364 1365 where (for RSA, without age-restriction) 1366 1367 1368 {::comment} 1369 1370 ⟨ᵧₖᵢ⟩ 1371 {:/} 1372 1373 ~~~ pseudocode 1374 (W1) history request (wallet) 1375 1376 msg = Gen-Msg(COIN_HISTORY_REQUEST, uint64(0x0)) 1377 sig = Ed25519-Sign(coin.priv, msg) 1378 ~~~ 1379 1380 {::comment} 1381 1382 ⟨ᵧₖᵢ⟩ 1383 {:/} 1384 1385 ~~~ pseudocode 1386 (E1) refresh secret lookup (exchange) 1387 1388 refresh_record = Lookup by coin.pub 1389 (ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = refresh_record 1390 if done: 1391 melt_info = (ɣ, link_info, ⟨blind_sigᵢ⟩) 1392 else: 1393 melt_info = (ɣ, link_info) 1394 ~~~ 1395 1396 {::comment} 1397 1398 ⟨ᵧₖᵢ⟩ 1399 {:/} 1400 1401 ~~~ pseudocode 1402 (W2) coin acquisition (wallet) 1403 1404 (ɣ, link_info, ⟨blind_sigᵢ⟩?) = melt_info 1405 (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) = link_info 1406 1407 for i in 0..n: 1408 denomᵢ = Lookup by h_denomᵢ 1409 for k in 0..κ: 1410 for i in 0..n: 1411 sharedₖᵢ = ECDH-Ed25519-Priv(coin.priv, transferₖᵢ.pub) 1412 (coinₖᵢ, blind_secretₖᵢ _, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1413 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1414 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1415 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1416 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1417 msg = Gen-Msg(WALLET_COIN_MELT, 1418 ( commitment | coin.h_denom | uint256(0x0) 1419 | value | denom.fee_refresh )) 1420 Check Ed25519-Verify(coin.pub, msg, sig) 1421 1422 if ⟨blind_sigᵢ⟩ returned: 1423 for i in 0..n: 1424 coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) 1425 Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) 1426 coinᵧᵢ.h_denom = h_denomᵢ 1427 Persist ⟨coinᵧᵢ⟩ 1428 ~~~ 1429 1430 ### Recoup {#refresh-recoup} 1431 1432 // todo 1433 1434 ## Transfer of E-Cash {#w2w} 1435 1436 // todo: introductory text 1437 1438 Transactions in E-Cash between wallets. 1439 Commonly referred to as peer-to-peer transactions. 1440 In Taler, interaction with exchange, therefore called wallet-to-wallet transactions. 1441 1442 ### Account Creation {#w2w-account} 1443 1444 ### Push Payment {#w2w-push} 1445 1446 // todo 1447 1448 ### Pull Payment {#w2w-pull} 1449 1450 // todo 1451 1452 # Security Considerations 1453 1454 \[ TBD \] 1455 1456 # IANA Considerations 1457 1458 None. 1459 1460 --- back 1461 1462 # Test Vectors 1463 1464 This appendix provides two sets of test vectors for testing Taler Protocol implementations. 1465 They are generated by going through the protocol operations in the following order: 1466 1467 1. Withdraw two coins `coin₀` and `coin₁` from a single `reserve` (cf. {{withdraw}}). 1468 2. Pay for one `order` with the full value of `coin₀` and a partial value of `coin₁` (cf. {{payment}}). 1469 3. Obtain a partial refund on `coin₀` used to pay for the `order` (cf. {{refund}}). 1470 4. Refresh the now-dirty `coin₁` to two new coins `coin₂` and `coin₃` (cf. {{refresh}}). 1471 5. Regenerate `coin₂` and `coin₃` with the knowledge of `coin₁` (cf. {{link}}). 1472 6. Create an `account` for w2w transfers (cf. {{w2w-account}}). 1473 7. Send a payment to `account` with the full value of `coin₂`, obtaining `coin₄` (cf. {{w2w-push}}). 1474 8. Request a payment to `account`, which is paid with the full value of `coin₄`, obtaining `coin₅` (cf. {{w2w-pull}}). 1475 9. Recoup the value of `coin₅` obtained via withdrawal from `account` (cf. {{withdraw-recoup}}). 1476 10. Recoup the value of `coin₃` obtained via refresh from `coin₁` (cf. {{refresh-recoup}}). 1477 1478 // todo: p2p sending full coins only works without fees, should we set fees to zero? 1479 1480 // todo: refund would be slightly more interesting with 2 coins being (partially) refunded, 1481 should we change to full refund coin0 + partial refund coin1 (coin1 value after fee_deposit + fee_refund should then match denom2 + denom3) 1482 1483 The test vectors in this document have been generated by the GNU Taler reference implementation written in C. 1484 All binary data is provided in hexadecimal notation. 1485 Big numbers for RSA are represented in big-endian byte order (most significant byte first). 1486 1487 ## Test Case 1 1488 1489 {::include ./test-vectors/test-case-1.md} 1490 1491 ## Test Case 2 1492 1493 # Change log 1494 1495 # Acknowledgments 1496 {:numbered="false"} 1497 1498 \[ TBD \] 1499 1500 This work was supported in part by the German Federal Ministry of 1501 Education and Research (BMBF) within the project Concrete Contracts.