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