draft-guetschow-taler-protocol.md (49268B)
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] 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 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 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 size and 525 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-related 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)) | 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 // todo: briefly introduce the three components wallet, exchange, merchant; maybe with ASCII diagram version 576 577 // todo: capitalize wallet, exchange, merchant? 578 579 ## Obtaining E-Cash 580 581 ### Withdrawal {#withdraw} 582 583 The wallet generates `n > 0` coins `⟨coinᵢ⟩` and requests `n` signatures `⟨blind_sigᵢ⟩` from the exchange, 584 attributing value to the coins according to `n` chosen denominations `⟨denomᵢ⟩`. 585 The total value and withdrawal fee (defined by the exchange per denomination) 586 must be smaller or equal to the amount stored in the single reserve used for withdrawal. 587 588 // todo: document TALER_MAX_COINS = 64 per operation (due to CS-encoding) 589 590 // todo: extend with extra roundtrip for CBS 591 592 ~~~ 593 wallet exchange 594 Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ 595 | | 596 +-----------------------------+ | 597 | (W1) reserve key generation | | 598 +-----------------------------+ | 599 | | 600 |----------- (bank transfer) ----------->| 601 | (subject: reserve.pub, amount: value) | 602 | | 603 | +------------------------------+ 604 | | Persist (reserve.pub, value) | 605 | +------------------------------+ 606 | | 607 +-----------------------------------+ | 608 | (W2) coin generation and blinding | | 609 +-----------------------------------+ | 610 | | 611 |-------------- /withdraw -------------->| 612 | (reserve.pub, planchets, sig) | 613 | | 614 | +--------------------------------+ 615 | | (E1) coin issuance and signing | 616 | +--------------------------------+ 617 | | 618 |<---------- (⟨blind_sigᵢ⟩) -------------| 619 | | 620 +----------------------+ | 621 | (W3) coin unblinding | | 622 +----------------------+ | 623 | | 624 ~~~ 625 626 where (for RSA, without age-restriction) 627 628 ~~~ 629 (W1) reserve key generation (wallet) 630 631 reserve = Ed25519-Keygen() 632 Persist (reserve, value) 633 ~~~ 634 635 The wallet derives coins and blinding secrets using a HKDF from a single seed per withdrawal operation, 636 together with an integer index. 637 This is strictly speaking an implementation detail since the seed is never revealed to any other party, 638 and might be chosen to be implemented differently. 639 640 // todo: blind_secret/coin.priv differently generated in TALER_EXCHANGE_post_withdraw_start/prepare_coins, double check with wallet-core (probably implementation detail here) 641 642 ~~~ 643 (W2) coin generation and blinding (wallet) 644 645 batch_seed = random(256) 646 Persist batch_seed 647 for i in 0..n: 648 coin_seedᵢ = HKDF(uint32(i), batch_seed, "taler-withdrawal-coin-derivation", 64) 649 blind_secretᵢ = coin_seedᵢ[32:] 650 coinᵢ.priv = coin_seedᵢ[:32] 651 coinᵢ.pub = Ed25519-GetPub(coinᵢ.priv) 652 h_denomᵢ = Hash-Denom(denomᵢ) 653 planchetᵢ = RSA-FDH-Blind(SHA-512(coinᵢ.pub), blind_secretᵢ, denomᵢ.pub) 654 h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) 655 planchets = (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) 656 msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, 657 ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ 658 | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) 659 sig = Ed25519-Sign(reserve.priv, msg) 660 ~~~ 661 662 ~~~ 663 (E1) coin issuance and signing (exchange) 664 665 (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) = planchets 666 for i in 0..n: 667 denomᵢ = Lookup by h_denomᵢ 668 Check denomᵢ known and not withdraw-expired 669 h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) 670 msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, 671 ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ 672 | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) 673 Check Ed25519-Verify(reserve.pub, msg, sig) 674 Check reserve KYC status ok or not needed 675 total = Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 676 Check-Subtract(reserve.balance, total) 677 for i in 0..n: 678 blind_sigᵢ = RSA-FDH-Sign(planchetᵢ, denomᵢ.priv) 679 Persist withdrawal // todo: what exactly? should be checked first for replay? 680 ~~~ 681 682 ~~~ 683 (W3) coin unblinding (wallet) 684 685 for i in 0..n: 686 coinᵢ.sig = RSA-FDH-Unblind(blind_sigᵢ, blind_secretᵢ, denomᵢ.pub) 687 Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) 688 coinᵢ.h_denom = h_denomᵢ 689 coinᵢ.blind_secret = blind_secretᵢ // todo: why save blind_secret, if batch_seed already persisted? 690 Persist ⟨coinᵢ⟩ 691 ~~~ 692 693 ### Recoup {#withdraw-recoup} 694 695 // todo 696 697 ## Payment with E-Cash 698 699 ### Payment and Deposit {#payment} 700 701 The wallet obtains `contract` information for an `order` from the merchant 702 after claiming it with a `nonce`. 703 Payment of the order is prepared by signing (partial) deposit authorizations `⟨depositᵢ⟩` with coins `⟨coinᵢ⟩` of certain denominations `⟨denomᵢ⟩`, 704 where the sum of all contributions (`contributionᵢ <= denomᵢ.value`) must match the `contract.price` plus potential deposit fees. 705 The payment is complete as soon as the merchant successfully redeems the deposit authorizations at the exchange. 706 707 Deposit could also be used directly by a wallet with its own payto and a minimal contract. 708 709 ~~~ 710 wallet merchant exchange 711 Knows ⟨coinᵢ⟩ Knows merchant.priv Knows exchange.priv 712 | Knows exchange, payto Knows ⟨denomᵢ⟩ 713 | | | 714 | +-----------------------+ | 715 | | (M1) order generation | | 716 | +-----------------------+ | 717 | | | 718 |<--- (QR-Code / NFC / URI) ---| | 719 | (order.{id,token?}) | | 720 | | | 721 +-----------------------+ | | 722 | (W1) nonce generation | | | 723 +-----------------------+ | | 724 | | | 725 |-- /orders/{order.id}/claim ->| | 726 | (nonce.pub, order.token?) | | 727 | | | 728 | +--------------------------+ | 729 | | (M2) contract generation | | 730 | +--------------------------+ | 731 | | | 732 |<-- (contract, merchant.pub, -| | 733 | sig) | | 734 | | | 735 +--------------------------+ | | 736 | (W2) payment preparation | | | 737 +--------------------------+ | | 738 | | | 739 |--- /orders/{order.id}/pay -->| | 740 | (⟨depositᵢ⟩) | | 741 | | | 742 | +--------------------------+ | 743 | | (M3) deposit preparation | | 744 | +--------------------------+ | 745 | | | 746 | |-------- /batch-deposit ----->| 747 | | (info, h_contract, ⟨depositᵢ⟩| 748 | | merchant.pub, sig) | 749 | | | 750 | | +--------------------+ 751 | | | (E1) deposit check | 752 | | +--------------------+ 753 | | | 754 | |<- (timestamp, exchange.pub, -| 755 | | sig) | 756 | | | 757 | +---------------------------+ | 758 | | (M4) deposit verification | | 759 | +---------------------------+ | 760 | | | 761 |<----------- (sig) -----------| | 762 | | | 763 +---------------------------+ | | 764 | (W3) payment verification | | | 765 +---------------------------+ | | 766 | | | 767 ~~~ 768 769 where (without age restriction, policy and wallet data hash) 770 771 ~~~ 772 (M1) order generation (merchant) 773 774 wire_salt = random(128) 775 determine id, price, info, token? 776 Persist order = (id, price, info, token?, wire_salt) 777 ~~~ 778 779 ~~~ 780 (W1) nonce generation (wallet) 781 782 nonce = Ed25519-Keygen() 783 Persist nonce.priv 784 ~~~ 785 786 Note that the private key of `nonce` is currently not used anywhere in the protocol. 787 However, it could be used in the future to prove ownership of an order transaction, 788 enabling use-cases such as "unclaiming" or transferring an order to another person, 789 or proving the payment without resorting to the individual coins. 790 791 ~~~ 792 (M2) contract generation (merchant) 793 794 Check order.token? == token? 795 h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) 796 determine timestamp, refund_deadline, wire_deadline 797 contract = (order.{id,price,info,token?}, exchange, h_wire, timestamp, refund_deadline, wire_deadline) 798 contract.nonce = nonce.pub 799 Persist contract 800 h_contract = SHA-512(canonicalJSON(contract)) 801 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 802 sig = Ed25519-Sign(merchant.priv, msg) 803 ~~~ 804 805 ~~~ 806 (W2) payment preparation (wallet) 807 808 h_contract = SHA-512(canonicalJSON(contract)) 809 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 810 Check Ed25519-Verify(merchant.pub, msg, sig) 811 Check contract.nonce == nonce 812 // TODO: double-check extra hash check? 813 // todo: maybe get rid of CoinSelection altogether by claiming we already know coinᵢ and contributionᵢ 814 ⟨selectionᵢ⟩ = CoinSelection(contract.{exchange,price}) TODO: include MarkDirty here 815 for i in 0..n: 816 (coinᵢ, denomᵢ, contributionᵢ) = selectionᵢ 817 msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, 818 ( h_contract | uint256(0x0) 819 | uint512(0x0) | contract.h_wire | coinᵢ.h_denom 820 | contract.timestamp | contract.refund_deadline 821 | contributionᵢ + denomᵢ.fee_deposit 822 | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) 823 sigᵢ = Ed25519-Sign(coinᵢ.priv, msgᵢ) 824 depositᵢ = (coinᵢ.{pub,sig,h_denom}, contributionᵢ, sigᵢ) 825 Persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) 826 ~~~ 827 828 // TODO: explain CoinSelection 829 830 ~~~ 831 (M3) deposit preparation (merchant) 832 833 Check Sum ⟨depositᵢ.contribution⟩ == contract.price 834 info.time = contract.{timestamp, wire_deadline, refund_deadline} 835 info.wire = (payto, wire_salt) 836 h_contract = SHA-512(canonicalJSON(contract)) 837 msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) 838 sig = Ed25519-Sign(merchant.priv, msg) 839 ~~~ 840 841 ~~~ 842 (E1) deposit check (exchange) 843 844 h_wire = HKDF(info.wire.wire_salt, info.wire.payto, "merchant-wire-signature", 64) 845 for i in 0..n: 846 coinᵢ = depositᵢ.coin 847 denomᵢ = Lookup by coinᵢ.h_denom 848 Check denomᵢ known and not deposit-expired 849 totalᵢ = depositᵢ.contribution + denomᵢ.fee_deposit 850 msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, 851 ( h_contract | uint256(0x0) 852 | uint512(0x0) | h_wire | coinᵢ.h_denom 853 | info.time.timestamp | info.time.refund_deadline 854 | totalᵢ 855 | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) 856 Check Ed25519-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) 857 Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) 858 Check-Subtract(coinᵢ.value, total) 859 Persist deposit-record 860 schedule bank transfer to payto 861 timestamp = now() 862 msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, 863 ( h_contract | h_wire | uint512(0x0) 864 | timestamp | info.time.wire_deadline 865 | info.time.refund_deadline 866 | Sum ⟨depositᵢ.contribution⟩ 867 | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) 868 sig = Ed25519-Sign(exchange.priv, msg) 869 ~~~ 870 871 ~~~ 872 (M2) deposit verification (merchant) 873 874 h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) 875 msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, 876 ( h_contract | h_wire | uint512(0x0) 877 | timestamp | contract.wire_deadline 878 | contract.refund_deadline 879 | Sum ⟨depositᵢ.contribution⟩ 880 | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) 881 Check Ed25519-Verify(exchange.pub, msg, sig) 882 msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) 883 sig = Ed25519-Sign(merchant.priv, msg) 884 ~~~~ 885 886 ~~~ 887 (W3) payment verification (wallet) 888 889 msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) 890 Check Ed25519-Verify(merchant.pub, msg, sig) 891 ~~~ 892 893 ### Refund {#refund} 894 895 A wallet can request a refund for an order from the merchant after it has been completed successfully 896 (cf. {{payment}}) and before the merchant has been paid out by the exchange (i.e., before `contract.wire_deadline`). 897 The merchant needs to approve the refund via its business logic, 898 and is free to decide the total amount of the refund 899 as well as which coins' deposit operations are (potentially partly) invalidated. 900 After the exchange has accepted the refund request, 901 the coins obtain their (partial) value back. 902 The wallet should proceed to refresh (cf. {{refresh}}) the coins before spending them again 903 to obtain unlinkability. 904 905 In case the wallet itself has used deposit to its own payto, 906 it can act as the merchant in the protocol below. 907 908 // todo: if proves practical, similar strucuture could be used for pay/deposit 909 (interaction) between wallet, merchant and exchange 910 911 ~~~ 912 wallet merchant exchange 913 Knows order.id Knows merchant.priv Knows deposit_record 914 Knows contract | for coinᵢ.pub 915 | | | 916 +---------------------+ | | 917 | (W1) refund request | | | 918 +---------------------+ | | 919 | | | 920 |- /orders/{order.id}/refund ->| | 921 | (h_contract) | | 922 | | | 923 | +------------------------+ | 924 | | (M1) refund processing | | 925 | +------------------------+ | 926 | | | 927 | |- /coins/{coinᵢ.pub}/refund ->| 928 | | (valueᵢ, h_contract, id, | 929 | | merchant.pub, sigᵢ) | 930 | | | 931 | | +-------------------+ 932 | | | (E1) refund check | 933 | | +-------------------+ 934 | | | 935 | |<--- (exchange.pub, sigᵢ) ----| 936 | | | 937 | +--------------------------+ | 938 | | (M2) refund confirmation | | 939 | +--------------------------+ | 940 | | | 941 |<-----(value, ⟨refundᵢ⟩,------| | 942 | merchant.pub) | | // todo: why merchant.pub if no sig transmitted? 943 | | | 944 +-----------------------+ | | 945 | (W2) refund reception | | | 946 +-----------------------+ | | 947 | | | 948 ~~~ 949 950 where (for RSA, without age-restriction) 951 952 {::comment} 953 954 ⟨ᵧₖᵢ⟩ 955 {:/} 956 957 ~~~ 958 (W1) refund request (wallet) 959 960 h_contract = SHA-512(canonicalJSON(contract)) 961 ~~~ 962 963 {::comment} 964 965 ⟨ᵧₖᵢ⟩ 966 {:/} 967 968 ~~~ 969 (M1) refund processing (merchant) 970 971 Check h_contract known and refund possible 972 time = now() 973 ⟨coinᵢ⟩ = Lookup by h_contract 974 id = ? 975 for i in 0..n: 976 denomᵢ = Lookup by coinᵢ.h_denom 977 valueᵢ = refund amount // todo: split wisely 978 msgᵢ = Gen-Msg(MERCHANT_REFUND, 979 ( h_contract | coinᵢ.pub | id | valueᵢ | denomᵢ.fee_refund )) 980 sigᵢ = Ed25519-Sign(merchant.priv, msgᵢ) 981 ~~~ 982 983 {::comment} 984 985 ⟨ᵧₖᵢ⟩ 986 {:/} 987 988 ~~~ 989 (E1) refund check and confirmation (exchange) 990 991 deposit_record = Lookup by h_contract // todo: needs to be persisted before with order.id and used coins! 992 Check coinᵢ.pub part of deposit_record 993 denomᵢ = Lookup by coinᵢ.pub 994 msgᵢ = Gen-Msg(MERCHANT_REFUND, 995 ( h_contract | coinᵢ.pub | id | valueᵢ | denomᵢ.fee_refund )) 996 Check Ed25519-Verify(merchant.pub, msgᵢ, sigᵢ) 997 Check valueᵢ >= denomᵢ.fee_refund 998 Check refund possible (prior to wire transfer deadline) 999 remove/update scheduled wire transfer 1000 mark coin part as unspent 1001 msgᵢ = Gen-Msg(MERCHANT_REFUND_OK, SHA-512(order.id)) // todo: hashing string without terminating \0 1002 sigᵢ = Ed25519-Sign(exchange.priv, msgᵢ) 1003 ~~~ 1004 1005 {::comment} 1006 1007 ⟨ᵧₖᵢ⟩ 1008 {:/} 1009 1010 ~~~ 1011 (M2) refund confirmation (merchant) 1012 1013 for i in 0..n: 1014 msgᵢ = Gen-Msg(MERCHANT_REFUND_OK, SHA-512(order.id)) // todo: hashing string without terminating \0 1015 Check Ed25519-Verify(exchange.pub, msgᵢ, sigᵢ) 1016 update business logic 1017 refundᵢ = (valueᵢ, sigᵢ, id, coinᵢ.pub, time) 1018 value = sum ⟨valueᵢ⟩ 1019 ~~~ 1020 1021 {::comment} 1022 1023 ⟨ᵧₖᵢ⟩ 1024 {:/} 1025 1026 ~~~ 1027 (W2) refund reception (wallet) 1028 1029 for i in 0..n: 1030 (valueᵢ, sigᵢ, id, coinᵢ.pub, time) = refundᵢ 1031 update persistent transaction information 1032 refresh ⟨coinᵢ⟩ 1033 ~~~ 1034 1035 ## Obtaining unlinkable change 1036 1037 ### Refresh {#refresh} 1038 1039 The wallet obtains `n` new coins `⟨coinᵢ⟩` of denominations `⟨denomᵢ⟩` 1040 in exchange for one old `coin` of denomination `denom` from the exchange. 1041 There are three reasons why a wallet needs to do this: 1042 1043 1. Obtaining unlinkable change after using only a part of the coin's value during a payment (cf. {{payment}}), 1044 i.e. where `contribution <= denom.value` 1045 2. Obtaining unlinkable change after a successful refund (cf. {{refund}}) 1046 3. Renewing a coin before it deposit-expires 1047 1048 The sum of the refresh fee of `denom` and the new denominations' values and withdrawal fees (defined by the exchange) 1049 must be smaller or equal to the residual value of the old `coin`. 1050 1051 The private key of each new coin candidate `⟨coinₖᵢ.priv⟩` is transitively derived from the old coin's private key `coin.priv` 1052 via a 512-bit secret `⟨sharedₖᵢ⟩` according to `Refresh-Derive`. 1053 The secret is regeneratable with the knowledge of `coin.priv` via the link protocol (cf. {{link}}). 1054 The derivation ensures that ownership of coins (knowledge of the private key) is correctly transferred, 1055 and thereby that value transfer among untrusted parties can only happen via payment and deposit, not via refresh. 1056 1057 ~~~ 1058 Refresh-Derive(shared, denom) = 1059 planchet_seed = HKDF(uint32(i), shared, "taler-coin-derivation", 64) 1060 blind_secret = HKDF("bks", planchet_seed, "", 32) 1061 coin.priv = HKDF("coin", planchet_seed, "", 32) 1062 coin.pub = Ed25519-GetPub(coin.priv) 1063 planchet = RSA-FDH-Blind(SHA-512(coin.pub), blind_secret, denomᵢ.pub) 1064 h_planchet = Hash-Planchet(planchet, denomᵢ) 1065 return (coin, blind_secret, planchet, h_planchet) 1066 ~~~ 1067 1068 Taler uses a cut-and-choose protocol with the fixed parameter `κ=3` to enforce correct derivation 1069 of `⟨sharedₖᵢ⟩` from a single seed per batch of planchets `⟨batch_seedₖ⟩` 1070 (in (κ-1)/κ of the cases, making income concealment for tax evasion purposes unpractical). 1071 1072 Refreshing consists of two parts: 1073 1074 1. Melting of the old coin and commiting to κ batches of blinded planchet candidates 1075 2. Revelation of κ-1 secrets `⟨revealed_seedₖ⟩` to prove the proper construction of the (revealed) batches of blinded planchet candidates. 1076 1077 ~~~ 1078 wallet exchange 1079 Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ 1080 Knows coin | 1081 | | 1082 +-------------------+ | 1083 | (W1) coin melting | | 1084 +-------------------+ | 1085 | | 1086 |---------------- /melt ---------------->| 1087 | (coin.{pub,sig,h_denom}, value, | 1088 | refresh_seed, planchets, sig) | 1089 | | 1090 | +---------------------------------------+ 1091 | | (E1) gamma selection and coin signing | 1092 | +---------------------------------------+ 1093 | | 1094 |<------ (ɣ, exchange.pub, sig) ---------| 1095 | | 1096 +------------------------+ | 1097 | (W2) secret revelation | | 1098 +------------------------+ | 1099 | | 1100 |------------ /reveal-melt ------------->| 1101 | (commitment, ⟨revealed_seedₖ⟩) | 1102 | | 1103 | +----------------------------+ 1104 | | (E2) commitment validation | 1105 | +----------------------------+ 1106 | | 1107 |<---------- (⟨blind_sigᵢ⟩) -------------| 1108 | | 1109 +----------------------+ | 1110 | (W3) coin unblinding | | 1111 +----------------------+ | 1112 | | 1113 ~~~ 1114 1115 where (for RSA, without age-restriction) 1116 1117 {::comment} 1118 // see TALER_EXCHANGE_get_melt_data 1119 ⟨batch_seedₖ⟩ // see TALER_refresh_expand_seed_to_kappa_batch_seeds 1120 ⟨transferₖᵢ.priv⟩ // see TALER_refresh_expand_batch_seed_to_transfer_data 1121 // todo: pretty sure ECDH-GetPub and Ed25519-GetPub are equivalent, if not, transferₖᵢ.pub needs to change 1122 h_planchetₖᵢ // see TALER_coin_ev_hash 1123 h_planchetsₖ // see TALER_wallet_blinded_planchet_details_hash 1124 commitment // see TALER_refresh_get_commitment 1125 1126 ⟨ᵧₖᵢ⟩ 1127 {:/} 1128 1129 ~~~ 1130 (W1) coin melting (wallet) 1131 1132 refresh_seed = random(256) 1133 ⟨batch_seedₖ⟩ = HKDF("refresh-batch-seeds", refresh_seed, coin.priv, k*64) 1134 for k in 0..κ: 1135 ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) 1136 for i in 0..n: 1137 transferₖᵢ.pub = ECDH-GetPub(transferₖᵢ.priv) 1138 sharedₖᵢ = ECDH-Ed25519-Pub(transferₖᵢ.priv, coin.pub) 1139 (coinₖᵢ, blind_secretₖᵢ, planchetₖᵢ, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1140 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1141 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1142 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1143 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1144 for i in 0..n: 1145 h_denomᵢ = Hash-Denom(denomᵢ) 1146 planchets = (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) 1147 msg = Gen-Msg(WALLET_COIN_MELT, 1148 ( commitment | coin.h_denom | uint256(0x0) 1149 | value | denom.fee_refresh )) 1150 sig = Ed25519-Sign(coin.priv, msg) 1151 Persist (coin.denom.pub, ...) // todo: double-check 1152 ~~~ 1153 1154 {::comment} 1155 1156 see TEH_handler_melt 1157 1158 ⟨ᵧₖᵢ⟩ 1159 {:/} 1160 1161 ~~~ 1162 (E1) gamma selection and coin signing (exchange) 1163 1164 denom = Lookup by coin.h_denom 1165 Check denom known and not deposit-expired 1166 Check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) 1167 Check coin.pub known and dirty 1168 (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) = planchets 1169 for i in 0..n: 1170 denomᵢ = Lookup by h_denomᵢ 1171 Check denomᵢ known and not withdraw-expired 1172 value' = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1173 Check value' == value 1174 Check-Subtract(coin.value, value) 1175 for k in 0..κ: 1176 for i in 0..n: 1177 h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) 1178 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1179 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1180 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1181 msg = Gen-Msg(WALLET_COIN_MELT, 1182 ( commitment | coin.h_denom | uint256(0x0) 1183 | value | denom.fee_refresh )) 1184 Check Ed25519-Verify(coin.pub, msg, sig) 1185 refresh_record = Lookup by commitment 1186 (ɣ, _, _, done, _) = refresh_record 1187 if refresh_record not found: 1188 ɣ = 0..κ at random 1189 for i in 0..n: 1190 blind_sigᵢ = RSA-FDH-Sign(planchetᵧᵢ, denomᵧᵢ.priv) 1191 link_info = (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) 1192 Persist refresh_record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) 1193 msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, 1194 ( commitment | uint32(ɣ) )) 1195 sig = Ed25519-Sign(exchange.priv, msg) 1196 ~~~ 1197 1198 {::comment} 1199 1200 // see src/lib/exchange_api_post-melt.c: handle_melt_finished 1201 // see src/lib/exchange_api_post-reveal-melt.c: perform_protocol 1202 1203 ⟨ᵧₖᵢ⟩ 1204 {:/} 1205 1206 ~~~ 1207 (W2) secret revelation (wallet) 1208 1209 Check exchange.pub known 1210 msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, 1211 ( commitment | ɣ )) 1212 Check Ed25519-Verify(exchange.pub, msg, sig) 1213 Persist refresh-challenge // what exactly? 1214 for k in 0..κ and k != ɣ: 1215 revealed_seedₖ = batch_seedₖ 1216 ~~~ 1217 1218 {::comment} 1219 1220 // see TEH_handler_reveal_melt 1221 1222 ⟨ᵧₖᵢ⟩ 1223 {:/} 1224 1225 ~~~ 1226 (E2) commitment validation (exchange) 1227 1228 refresh_record = Lookup by commitment 1229 (ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = refresh_record 1230 Check not done // todo: sure? 1231 for k in 0..κ and k != ɣ: 1232 ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) 1233 for i in 0..n: 1234 transferₖᵢ.pub = ECDH-GetPub(transferₖᵢ.priv) 1235 sharedₖᵢ = ECDH-Ed25519-Pub(transferₖᵢ.priv, coin.pub) 1236 (_, _, _, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1237 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1238 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1239 commitment' = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1240 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1241 Check commitment == commitment' 1242 Persist refresh_record = (_, _, _, true, _) 1243 ~~~ 1244 1245 {::comment} 1246 1247 // see src/lib/exchange_api_post-reveal-melt.c: reveal_melt_ok 1248 1249 ⟨ᵧₖᵢ⟩ 1250 {:/} 1251 1252 ~~~ 1253 (W3) coin unblinding (wallet) 1254 1255 for i in 0..n: 1256 coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) 1257 Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) 1258 coinᵧᵢ.h_denom = h_denomᵢ 1259 Persist ⟨coinᵧᵢ⟩ 1260 ~~~ 1261 1262 ### Link {#link} 1263 1264 Coins ⟨coinᵧᵢ⟩ obtained via the refresh protocol (cf. {{refresh}}) can be regenerated 1265 with the knowledge of the old coin's private key `coin.priv` using the link protocol, 1266 integrated in the coin history endpoint. 1267 1268 ~~~ 1269 wallet exchange 1270 Knows coin Knows refresh_record for coin.pub 1271 | | 1272 +----------------------+ | 1273 | (W1) history request | | 1274 +----------------------+ | 1275 | | 1276 |------ /coins/{coin.pub}/history ------>| 1277 | (sig) | 1278 | | 1279 | +----------------------------+ 1280 | | (E1) refresh secret lookup | 1281 | +----------------------------+ 1282 | | 1283 |<------------- (melt_info) -------------| 1284 | | 1285 +-----------------------+ | 1286 | (W2) coin acquisition | | 1287 +-----------------------+ | 1288 | | 1289 ~~~ 1290 1291 where (for RSA, without age-restriction) 1292 1293 1294 {::comment} 1295 1296 ⟨ᵧₖᵢ⟩ 1297 {:/} 1298 1299 ~~~ 1300 (W1) history request (wallet) 1301 1302 msg = Gen-Msg(COIN_HISTORY_REQUEST, uint64(0x0)) 1303 sig = Ed25519-Sign(coin.priv, msg) 1304 ~~~ 1305 1306 {::comment} 1307 1308 ⟨ᵧₖᵢ⟩ 1309 {:/} 1310 1311 ~~~ 1312 (E1) refresh secret lookup (exchange) 1313 1314 refresh_record = Lookup by coin.pub 1315 (ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = refresh_record 1316 if done: 1317 melt_info = (ɣ, link_info, ⟨blind_sigᵢ⟩) 1318 else: 1319 melt_info = (ɣ, link_info) 1320 ~~~ 1321 1322 {::comment} 1323 1324 ⟨ᵧₖᵢ⟩ 1325 {:/} 1326 1327 ~~~ 1328 (W2) coin acquisition (wallet) 1329 1330 (ɣ, link_info, ⟨blind_sigᵢ⟩?) = melt_info 1331 (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) = link_info 1332 1333 for i in 0..n: 1334 denomᵢ = Lookup by h_denomᵢ 1335 for k in 0..κ: 1336 for i in 0..n: 1337 sharedₖᵢ = ECDH-Ed25519-Priv(coin.priv, transferₖᵢ.pub) 1338 (coinₖᵢ, blind_secretₖᵢ _, h_planchetₖᵢ) = Refresh-Derive(sharedₖᵢ, denomᵢ) 1339 h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) 1340 value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ 1341 commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value 1342 | SHA-512( ⟨h_planchetsₖ⟩ ) ) 1343 msg = Gen-Msg(WALLET_COIN_MELT, 1344 ( commitment | coin.h_denom | uint256(0x0) 1345 | value | denom.fee_refresh )) 1346 Check Ed25519-Verify(coin.pub, msg, sig) 1347 1348 if ⟨blind_sigᵢ⟩ returned: 1349 for i in 0..n: 1350 coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) 1351 Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) 1352 coinᵧᵢ.h_denom = h_denomᵢ 1353 Persist ⟨coinᵧᵢ⟩ 1354 ~~~ 1355 1356 ### Recoup {#refresh-recoup} 1357 1358 // todo 1359 1360 ## Transfer of E-Cash {#w2w} 1361 1362 // todo: introductory text 1363 1364 Transactions in E-Cash between wallets. 1365 Commonly referred to as peer-to-peer transactions. 1366 In Taler, interaction with exchange, therefore called wallet-to-wallet transactions. 1367 1368 ### Account Creation {#w2w-account} 1369 1370 ### Push Payment {#w2w-push} 1371 1372 // todo 1373 1374 ### Pull Payment {#w2w-pull} 1375 1376 // todo 1377 1378 # Security Considerations 1379 1380 \[ TBD \] 1381 1382 # IANA Considerations 1383 1384 None. 1385 1386 --- back 1387 1388 # Test Vectors 1389 1390 This appendix provides two sets of test vectors for testing Taler Protocol implementations. 1391 They are generated by going through the protocol operations in the following order: 1392 1393 1. Withdraw two coins `coin₀` and `coin₁` from a single `reserve` (cf. {{withdraw}}). 1394 2. Pay for one `order` with the full value of `coin₀` and a partial value of `coin₁` (cf. {{payment}}). 1395 3. Obtain a partial refund for `coin₀` used to pay for the `order` (cf. {{refund}}). 1396 4. Refresh the now-dirty `coin₁` to two new coins `coin₂` and `coin₃` (cf. {{refresh}}). 1397 5. Regenerate `coin₂` and `coin₃` with the knowledge of `coin₁` (cf. {{link}}). 1398 6. Create an `account` for w2w transfers (cf. {{w2w-account}}). 1399 7. Send a payment to `account` with the full value of `coin₂`, obtaining `coin₅` (cf. {{w2w-push}}). 1400 8. Request a payment to `account`, which is paid with the full value of `coin₅`, obtaining `coin₆` (cf. {{w2w-pull}}). 1401 9. Recoup the value of `coin₆` obtained via withdrawal from `account` (cf. {{withdraw-recoup}}). 1402 10. Recoup the value of `coin₃` obtained via refresh from `coin₁` (cf. {{refresh-recoup}}). 1403 1404 The test vectors in this document have been generated by the GNU Taler reference implementation written in C. 1405 All binary data is provided in hexadecimal notation. 1406 1407 ## Test Case 1 1408 1409 ~~~ 1410 denom₀.priv = XXX 1411 denom₀.pub = XXX 1412 denom₀.value = XXX 1413 denom₀.fee_withdraw = XXX 1414 denom₁.priv = XXX 1415 denom₁.pub = XXX 1416 denom₁.value = XXX 1417 denom₁.fee_withdraw = XXX 1418 ~~~ 1419 1420 1421 ### Withdrawal {#tc1-withdraw} 1422 1423 ~~~ 1424 (W1) reserve key generation (wallet) 1425 1426 reserve.priv = XXX 1427 reserve.pub = XXX 1428 reserve.balance = XXX 1429 ~~~ 1430 1431 ~~~ 1432 (W2) coin generation and blinding (wallet) 1433 1434 batch_seed = XXX 1435 coin_seed₀ = XXX 1436 coin_seed₁ = XXX 1437 blind_secret₀ = XXX 1438 blind_secret₁ = XXX 1439 coin₀.priv = XXX 1440 coin₁.priv = XXX 1441 coin₀.pub = XXX 1442 coin₁.pub = XXX 1443 h_denom₀ = XXX 1444 h_denom₁ = XXX 1445 planchet₀ = XXX 1446 planchet₁ = XXX 1447 h_planchet₀ = XXX 1448 h_planchet₁ = XXX 1449 msg = XXX 1450 sig = XXX 1451 ~~~ 1452 1453 ~~~ 1454 (E1) coin issuance and signing (exchange) 1455 1456 total = XXX 1457 blind_sig₀ = XXX 1458 blind_sig₁ = XXX 1459 ~~~ 1460 1461 ~~~ 1462 (W3) coin unblinding (wallet) 1463 1464 coin₀.sig = XXX 1465 coin₁.sig = XXX 1466 ~~~ 1467 1468 ### Payment and Deposit {#tc1-payment} 1469 1470 ### Refund {#tc1-refund} 1471 1472 ### Refresh {#tc1-refresh} 1473 1474 ### Link {#tc1-link} 1475 1476 ### Account Creation {#tc1-w2w-account} 1477 1478 ### Push Payment {#tc1-w2w-push} 1479 1480 ### Pull Payment {#tc1-w2w-pull} 1481 1482 ### Recoup Withdrawal {#tc1-withdraw-recoup} 1483 1484 ### Recoup Refresh {#tc1-refresh-recoup} 1485 1486 ## Test Case 2 1487 1488 # Change log 1489 1490 # Acknowledgments 1491 {:numbered="false"} 1492 1493 \[ TBD \] 1494 1495 This work was supported in part by the German Federal Ministry of 1496 Education and Research (BMBF) within the project Concrete Contracts.