lsd0009

LSD0009: The GNU Taler Protocol
Log | Files | Refs | README

commit cd0e8a29ede3335c410f0893b32b54684ea88643
parent 56d4ae91712dcb8b3a151db2e8af3f97af6c5e10
Author: Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
Date:   Wed,  8 Apr 2026 11:56:43 +0200

protocol: factor out and document common functions

Diffstat:
Mdraft-guetschow-taler-protocol.md | 218+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Mdraft-guetschow-taler-protocol.xml | 617++++++++++++++++++++++++++++++++++++++++++-------------------------------------
2 files changed, 449 insertions(+), 386 deletions(-)

diff --git a/draft-guetschow-taler-protocol.md b/draft-guetschow-taler-protocol.md @@ -60,10 +60,15 @@ Use at your own risk! - `len(a)` denotes the length in bytes of the byte string a - `padZero(y, a)` denotes the byte string a, zero-padded to the length of y bytes - `bits(x)`/`bytes(x)` denotes the minimal number of bits/bytes necessary to represent the multiple precision integer x -- `uint(y, x)` denotes the `y` least significant bits of the integer `x` encoded in network byte order (big endian) -- `uint16(x)`/`uint32(x)`/`uint64(x)`/`uint256(x)`/`uint512(x)` is equivalent to `uint(16, x)`/`uint(32, x)`/`uint(64, x)`/`uint(256, x)`/`uint(512, x)`, respectively +- `uint(y, x)` denotes the `y` least significant bits of the integer `x`, zero-padded and encoded in network byte order (big endian) +- `uintY(x)` where `Y` is a positive integer number is equivalent to `uint(Y, x)` - `random(y)` denotes a randomly generated sequence of y bits - `a * b (mod N)` / `a ** b (mod N)` denotes the multiplication / exponentiation of multiple precision integers a and b, modulo N +- `for`, `if`, variable assignment `=`, and conditional operators are to be interpreted like their Python/Julia equivalents +- `data.key` denotes the property `key` on the object `data` +- `0..n` denotes the exclusive range of integer numbers from `0` to `n-1` +- `⟨dataᵢ⟩` within a context of `i = 0..n` denotes `n` objects `dataᵢ`, represented in memory as a continuous array +- `⟨dataᵢ.key⟩` within a context of `i = 0..n` denotes an array of the `n` properties `key` of all `n` objects `dataᵢ` # Cryptographic Primitives @@ -342,7 +347,7 @@ out = (data == exp) # Datatypes and Notation -## Amount +## Amounts {#amounts} Amounts are represented in Taler as positive fixed-point values consisting of `value` as the non-negative integer part of the base currency, @@ -389,21 +394,32 @@ out = uint32(len(msg)) | uint32(purpose) | msg ## Helper Functions +There are a certain number of functions which are often needed, +and therefore omit the typical function syntax of using parentheses: + +- `Knows data` specifies `data` that is known a priori at the start of the protocol operation +- `Check cond` verifies that the boolean condition or variable `cond` is true, + or aborts the protocol operation otherwise +- `Persist data` persists the given `data` to the local database +- `data = Lookup by key` retrieves previously persisted `data` by the given `key` +- `Sum ⟨dataᵢ⟩` is valid for numerical objects `dataᵢ` including amounts (cf. {{amounts}}), + and denotes the numerical sum of these objects + +Some more functions that are used throughout {#protocol}: + ~~~ -- Denom-Hash: SHA-512(uint32(0) | uint32(1) | denomᵢ.pub) -- Planchet-Hash: SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) -- check-decrement: check reserve.balance >= total, persist reserve.balance -= total -- refresh-derivation (only for refresh/link) -- persist-{type} (key1, key2, ...) => (val1, val2, ...) -- lookup-{type} (key1) -- check bool -- knows data -- sum ⟨dataᵢ⟩ -- ⟨dataᵢ⟩ -- for/if +Hash-Denom(denom) = + SHA-512(uint32(0) | uint32(1) | denom.pub) + +Hash-Planchet(planchet, denom) = + SHA-512( SHA-512( denom.pub ) | uint32(0x1) | planchet ) + +Check-Subtract(value, subtrahend) = + Check value >= subtrahend + Persist value -= subtrahend ~~~ -# The Taler Crypto Protocol +# The Taler Crypto Protocol {#procotol} ## Withdrawal {#withdrawal} @@ -418,7 +434,7 @@ must be smaller or equal to the amount stored in the single reserve used for wit ~~~ wallet exchange -knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ +Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ | | +-----------------------------+ | | (W1) reserve key generation | | @@ -428,7 +444,7 @@ knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ | (subject: reserve.pub, amount: value) | | | | +------------------------------+ - | | persist (reserve.pub, value) | + | | Persist (reserve.pub, value) | | +------------------------------+ | | +-----------------------------------+ | @@ -456,7 +472,7 @@ where (for RSA, without age-restriction) (W1) reserve key generation (wallet) reserve = EdDSA-Keygen() -persist (reserve, value) +Persist (reserve, value) ~~~ The wallet derives coins and blinding secrets using a HKDF from a single seed per withdrawal operation, @@ -470,18 +486,18 @@ and might be chosen to be implemented differently. (W2) coin generation and blinding (wallet) batch_seed = random(256) -persist batch_seed +Persist batch_seed for i in 0..n: coin_seedᵢ = HKDF(uint32(i), batch_seed, "taler-withdrawal-coin-derivation", 64) blind_secretᵢ = coin_seedᵢ[32:] coinᵢ.priv = coin_seedᵢ[:32] coinᵢ.pub = EdDSA-GetPub(coinᵢ.priv) - h_denomᵢ = SHA-512(uint32(0) | uint32(1) | denomᵢ.pub) + h_denomᵢ = Hash-Denom(denomᵢ) planchetᵢ = RSA-FDH-Blind(SHA-512(coinᵢ.pub), blind_secretᵢ, denomᵢ.pub) - h_planchetᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) + h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) planchets = (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, - ( sum( ⟨denomᵢ.value⟩ ) | sum( ⟨denomᵢ.fee_withdraw⟩ ) + ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) sig = EdDSA-Sign(reserve.priv, msg) ~~~ @@ -491,20 +507,19 @@ sig = EdDSA-Sign(reserve.priv, msg) (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) = planchets for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) - check denomᵢ known and not withdrawal-expired - h_planchetᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) + denomᵢ = Lookup by h_denomᵢ + Check denomᵢ known and not withdrawal-expired + h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, - ( sum( ⟨denomᵢ.value⟩ ) | sum( ⟨denomᵢ.fee_withdraw⟩ ) + ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) -check EdDSA-Verify(reserve.pub, msg, sig) -check reserve KYC status ok or not needed -total = sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) -check reserve.balance >= total -persist reserve.balance -= total +Check EdDSA-Verify(reserve.pub, msg, sig) +Check reserve KYC status ok or not needed +total = Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ +Check-Subtract(reserve.balance, total) for i in 0..n: blind_sigᵢ = RSA-FDH-Sign(planchetᵢ, denomᵢ.priv) -persist withdrawal // todo: what exactly? should be checked first for replay? +Persist withdrawal // todo: what exactly? should be checked first for replay? ~~~ ~~~ @@ -512,10 +527,10 @@ persist withdrawal // todo: what exactly? should be checked first for replay? for i in 0..n: coinᵢ.sig = RSA-FDH-Unblind(blind_sigᵢ, blind_secretᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) coinᵢ.h_denom = h_denomᵢ coinᵢ.blind_secret = blind_secretᵢ // todo: why save blind_secret, if batch_seed already persisted? -persist ⟨coinᵢ⟩ +Persist ⟨coinᵢ⟩ ~~~ ## Payment {#payment} @@ -528,8 +543,8 @@ The payment is complete as soon as the merchant successfully redeems the deposit ~~~ wallet merchant -knows ⟨coinᵢ⟩ knows merchant.priv - knows exchange, payto +Knows ⟨coinᵢ⟩ Knows merchant.priv + Knows exchange, payto | | | +-----------------------+ | | (M1) order generation | @@ -577,14 +592,14 @@ where (without age restriction, policy and wallet data hash) wire_salt = random(128) determine id, price, info, token? -persist order = (id, price, info, token?, wire_salt) +Persist order = (id, price, info, token?, wire_salt) ~~~ ~~~ (W1) nonce generation (wallet) nonce = EdDSA-Keygen() -persist nonce.priv +Persist nonce.priv ~~~ Note that the private key of `nonce` is currently not used anywhere in the protocol. @@ -595,12 +610,12 @@ or proving the payment without resorting to the individual coins. ~~~ (M2) contract generation (merchant) -check order.token? == token? +Check order.token? == token? h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) determine timestamp, refund_deadline, wire_deadline contract = (order.{id,price,info,token?}, exchange, h_wire, timestamp, refund_deadline, wire_deadline) contract.nonce = nonce.pub -persist contract +Persist contract h_contract = SHA-512(canonicalJSON(contract)) msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) sig = EdDSA-Sign(merchant.priv, msg) @@ -611,8 +626,8 @@ sig = EdDSA-Sign(merchant.priv, msg) h_contract = SHA-512(canonicalJSON(contract)) msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) -check EdDSA-Verify(merchant.pub, msg, sig) -check contract.nonce == nonce +Check EdDSA-Verify(merchant.pub, msg, sig) +Check contract.nonce == nonce // TODO: double-check extra hash check? // todo: maybe get rid of CoinSelection altogether by claiming we already know coinᵢ and contributionᵢ ⟨selectionᵢ⟩ = CoinSelection(contract.{exchange,price}) TODO: include MarkDirty here @@ -626,7 +641,7 @@ for i in 0..n: | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) sigᵢ = EdDSA-Sign(coinᵢ.priv, msgᵢ) depositᵢ = (coinᵢ.{pub,sig,h_denom}, contributionᵢ, sigᵢ) -persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) +Persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) ~~~ // TODO: explain CoinSelection @@ -636,8 +651,8 @@ persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) ~~~ (M3) deposit check (merchant) -check sum( ⟨depositᵢ.contribution⟩ ) == contract.price -check Deposit(⟨depositᵢ⟩) +Check Sum ⟨depositᵢ.contribution⟩ == contract.price +Check Deposit(⟨depositᵢ⟩) msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) sig = EdDSA-Sign(merchant.priv, msg) ~~~ @@ -646,7 +661,7 @@ sig = EdDSA-Sign(merchant.priv, msg) (W3) payment verification (wallet) msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) -check EdDSA-Verify(merchant.pub, msg, sig) +Check EdDSA-Verify(merchant.pub, msg, sig) ~~~ ## Deposit {#deposit} @@ -657,10 +672,10 @@ Deposit could also be used directly by a wallet with its own payto and a minimal ~~~ merchant exchange -knows exchange.pub knows exchange.priv -knows merchant.priv knows ⟨denomᵢ⟩ -knows payto, wire_salt | -knows contract, ⟨depositᵢ⟩ | +Knows exchange.pub Knows exchange.priv +Knows merchant.priv Knows ⟨denomᵢ⟩ +Knows payto, wire_salt | +Knows contract, ⟨depositᵢ⟩ | | | +--------------------------+ | | (M1) deposit preparation | | @@ -700,8 +715,8 @@ sig = EdDSA-Sign(merchant.priv, msg) h_wire = HKDF(info.wire.wire_salt, info.wire.payto, "merchant-wire-signature", 64) for i in 0..n: coinᵢ = depositᵢ.coin - denomᵢ = Denom-Lookup(coinᵢ.h_denom) - check denomᵢ known and not deposit-expired // todo: check could be included in Denom-Lookup + denomᵢ = Lookup by coinᵢ.h_denom + Check denomᵢ known and not deposit-expired totalᵢ = depositᵢ.contribution + denomᵢ.fee_deposit msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, ( h_contract | uint256(0x0) @@ -709,18 +724,17 @@ for i in 0..n: | info.time.timestamp | info.time.refund_deadline | totalᵢ | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) - check EdDSA-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) - check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) - check coinᵢ.value >= totalᵢ - persist coinᵢ.value -= totalᵢ -persist deposit-record + Check EdDSA-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) + Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) + Check-Subtract(coinᵢ.value, total) +Persist deposit-record schedule bank transfer to payto timestamp = now() msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, ( h_contract | h_wire | uint512(0x0) | timestamp | info.time.wire_deadline | info.time.refund_deadline - | sum( ⟨depositᵢ.contribution⟩ ) + | Sum ⟨depositᵢ.contribution⟩ | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) sig = EdDSA-Sign(exchange.priv, msg) ~~~ @@ -733,19 +747,21 @@ msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, ( h_contract | h_wire | uint512(0x0) | timestamp | contract.wire_deadline | contract.refund_deadline - | sum( ⟨depositᵢ.contribution⟩ ) + | Sum ⟨depositᵢ.contribution⟩ | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) -check EdDSA-Verify(exchange.pub, msg, sig) +Check EdDSA-Verify(exchange.pub, msg, sig) ~~~ ## Refresh {#refresh} // todo: add introductory text +// todo: factor out Derive (from shared secret onwards) + ~~~ wallet exchange -knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ -knows coin | +Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ +Knows coin | | | +-------------------+ | | (W1) coin melting | | @@ -811,19 +827,19 @@ for k in 0..κ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) for i in 0..n: - h_denomᵢ = SHA-512(uint32(0) | uint32(0x1) | denomᵢ.pub) + h_denomᵢ = Hash-Denom(denomᵢ) planchets = (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) sig = EdDSA-Sign(coin.priv, msg) -persist (coin.denom.pub, ...) // todo: double-check +Persist (coin.denom.pub, ...) // todo: double-check ~~~ {::comment} @@ -836,35 +852,35 @@ see TEH_handler_melt ~~~ (E1) gamma selection and coin signing (exchange) -denom = Denom-Lookup(coin.h_denom) -check denom known and not refresh-expired -check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) -check coin.pub known and dirty +denom = Lookup by coin.h_denom +Check denom known and not refresh-expired +Check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) +Check coin.pub known and dirty (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) = planchets for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) - check denomᵢ known and not withdraw-expired -value' = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) -check value' == value -check coin.value >= value -persist coin.value -= value + denomᵢ = Lookup by h_denomᵢ + Check denomᵢ known and not withdraw-expired +value' = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ +Check value' == value +Check-Subtract(coin.value, value) for k in 0..κ: for i in 0..n: - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) -check EdDSA-Verify(coin.pub, msg, sig) -(ɣ, _, _, done, _) = lookup refresh-record(commitment) -if refresh-record not found: +Check EdDSA-Verify(coin.pub, msg, sig) +refresh_record = Lookup by commitment +(ɣ, _, _, done, _) = refresh_record +if refresh_record not found: ɣ = 0..κ at random for i in 0..n: blind_sigᵢ = RSA-FDH-Sign(planchetᵧᵢ, denomᵧᵢ.priv) link_info = (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) - persist refresh-record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) + Persist refresh_record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, ( commitment | uint32(ɣ) )) sig = EdDSA-Sign(exchange.priv, msg) @@ -881,11 +897,11 @@ sig = EdDSA-Sign(exchange.priv, msg) ~~~ (W2) secret revelation (wallet) -check exchange.pub known +Check exchange.pub known msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, ( commitment | ɣ )) -check EdDSA-Verify(exchange.pub, msg, sig) -persist refresh-challenge // what exactly? +Check EdDSA-Verify(exchange.pub, msg, sig) +Persist refresh-challenge // what exactly? for k in 0..κ and k != ɣ: revealed_seedₖ = batch_seedₖ ~~~ @@ -900,8 +916,9 @@ for k in 0..κ and k != ɣ: ~~~ (E2) commitment validation (exchange) -(ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = lookup refresh-record(commitment) -check not done // todo: sure? +refresh_record = Lookup by commitment +(ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = refresh_record +Check not done // todo: sure? for k in 0..κ and k != ɣ: ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) for i in 0..n: @@ -912,13 +929,13 @@ for k in 0..κ and k != ɣ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment' = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) -check commitment == commitment' -persist refresh-record = (_, _, _, true, _) +Check commitment == commitment' +Persist refresh_record = (_, _, _, true, _) ~~~ {::comment} @@ -933,9 +950,9 @@ persist refresh-record = (_, _, _, true, _) for i in 0..n: coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) coinᵧᵢ.h_denom = h_denomᵢ - persist ⟨coinᵧᵢ⟩ + Persist ⟨coinᵧᵢ⟩ ~~~ ### Link {#link} @@ -944,7 +961,7 @@ for i in 0..n: ~~~ wallet exchange -knows coin knows refresh-record for coin +Knows coin Knows refresh_record for coin | | +----------------------+ | | (W1) history request | | @@ -988,7 +1005,8 @@ sig = EdDSA-Sign(coin.priv, msg) ~~~ (E1) refresh secret lookup (exchange) -(ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = lookup refresh-record(coin.pub) +refresh_record = Lookup by coin.pub +(ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = refresh_record if done: melt_info = (ɣ, link_info, ⟨blind_sigᵢ⟩) else: @@ -1007,7 +1025,7 @@ else: (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) = link_info for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) + denomᵢ = Lookup by h_denomᵢ for k in 0..κ: for i in 0..n: sharedₖᵢ = ECDH-EdDSA(coin.priv, transferₖᵢ.pub) @@ -1016,22 +1034,22 @@ for k in 0..κ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) -check EdDSA-Verify(coin.pub, msg, sig) +Check EdDSA-Verify(coin.pub, msg, sig) if ⟨blind_sigᵢ⟩ returned: for i in 0..n: coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) coinᵧᵢ.h_denom = h_denomᵢ - persist ⟨coinᵧᵢ⟩ + Persist ⟨coinᵧᵢ⟩ ~~~ ## Refund {#refund} diff --git a/draft-guetschow-taler-protocol.xml b/draft-guetschow-taler-protocol.xml @@ -64,10 +64,10 @@ Use at your own risk!</t> <t><tt>bits(x)</tt>/<tt>bytes(x)</tt> denotes the minimal number of bits/bytes necessary to represent the multiple precision integer x</t> </li> <li> - <t><tt>uint(y, x)</tt> denotes the <tt>y</tt> least significant bits of the integer <tt>x</tt> encoded in network byte order (big endian)</t> + <t><tt>uint(y, x)</tt> denotes the <tt>y</tt> least significant bits of the integer <tt>x</tt>, zero-padded and encoded in network byte order (big endian)</t> </li> <li> - <t><tt>uint16(x)</tt>/<tt>uint32(x)</tt>/<tt>uint64(x)</tt>/<tt>uint256(x)</tt>/<tt>uint512(x)</tt> is equivalent to <tt>uint(16, x)</tt>/<tt>uint(32, x)</tt>/<tt>uint(64, x)</tt>/<tt>uint(256, x)</tt>/<tt>uint(512, x)</tt>, respectively</t> + <t><tt>uintY(x)</tt> where <tt>Y</tt> is a positive integer number is equivalent to <tt>uint(Y, x)</tt></t> </li> <li> <t><tt>random(y)</tt> denotes a randomly generated sequence of y bits</t> @@ -75,6 +75,21 @@ Use at your own risk!</t> <li> <t><tt>a * b (mod N)</tt> / <tt>a ** b (mod N)</tt> denotes the multiplication / exponentiation of multiple precision integers a and b, modulo N</t> </li> + <li> + <t><tt>for</tt>, <tt>if</tt>, variable assignment <tt>=</tt>, and conditional operators are to be interpreted like their Python/Julia equivalents</t> + </li> + <li> + <t><tt>data.key</tt> denotes the property <tt>key</tt> on the object <tt>data</tt></t> + </li> + <li> + <t><tt>0..n</tt> denotes the exclusive range of integer numbers from <tt>0</tt> to <tt>n-1</tt></t> + </li> + <li> + <t><tt>⟨dataᵢ⟩</tt> within a context of <tt>i = 0..n</tt> denotes <tt>n</tt> objects <tt>dataᵢ</tt>, represented in memory as a continuous array</t> + </li> + <li> + <t><tt>⟨dataᵢ.key⟩</tt> within a context of <tt>i = 0..n</tt> denotes an array of the <tt>n</tt> properties <tt>key</tt> of all <tt>n</tt> objects <tt>dataᵢ</tt></t> + </li> </ul> </section> <section anchor="cryptographic-primitives"> @@ -335,8 +350,8 @@ out = (data == exp) </section> <section anchor="datatypes-and-notation"> <name>Datatypes and Notation</name> - <section anchor="amount"> - <name>Amount</name> + <section anchor="amounts"> + <name>Amounts</name> <t>Amounts are represented in Taler as positive fixed-point values consisting of <tt>value</tt> as the non-negative integer part of the base currency, the <tt>fraction</tt> given in units of one hundred millionth (1e-8) of the base currency, @@ -379,22 +394,42 @@ out = uint32(len(msg)) | uint32(purpose) | msg </section> <section anchor="helper-functions"> <name>Helper Functions</name> + <t>There are a certain number of functions which are often needed, +and therefore omit the typical function syntax of using parentheses:</t> + <ul spacing="normal"> + <li> + <t><tt>Knows data</tt> specifies <tt>data</tt> that is known a priori at the start of the protocol operation</t> + </li> + <li> + <t><tt>Check cond</tt> verifies that the boolean condition or variable <tt>cond</tt> is true, +or aborts the protocol operation otherwise</t> + </li> + <li> + <t><tt>Persist data</tt> persists the given <tt>data</tt> to the local database</t> + </li> + <li> + <t><tt>data = Lookup by key</tt> retrieves previously persisted <tt>data</tt> by the given <tt>key</tt></t> + </li> + <li> + <t><tt>Sum ⟨dataᵢ⟩</tt> is valid for numerical objects <tt>dataᵢ</tt> including amounts (cf. <xref target="amounts"/>), +and denotes the numerical sum of these objects</t> + </li> + </ul> + <t>Some more functions that are used throughout {#protocol}:</t> <artwork><![CDATA[ -- Denom-Hash: SHA-512(uint32(0) | uint32(1) | denomᵢ.pub) -- Planchet-Hash: SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) -- check-decrement: check reserve.balance >= total, persist reserve.balance -= total -- refresh-derivation (only for refresh/link) -- persist-{type} (key1, key2, ...) => (val1, val2, ...) -- lookup-{type} (key1) -- check bool -- knows data -- sum ⟨dataᵢ⟩ -- ⟨dataᵢ⟩ -- for/if +Hash-Denom(denom) = + SHA-512(uint32(0) | uint32(1) | denom.pub) + +Hash-Planchet(planchet, denom) = + SHA-512( SHA-512( denom.pub ) | uint32(0x1) | planchet ) + +Check-Subtract(value, subtrahend) = + Check value >= subtrahend + Persist value -= subtrahend ]]></artwork> </section> </section> - <section anchor="the-taler-crypto-protocol"> + <section anchor="procotol"> <name>The Taler Crypto Protocol</name> <section anchor="withdrawal"> <name>Withdrawal</name> @@ -406,7 +441,7 @@ must be smaller or equal to the amount stored in the single reserve used for wit <t>// todo: extend with extra roundtrip for CBS</t> <artwork><![CDATA[ wallet exchange -knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ +Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ | | +-----------------------------+ | | (W1) reserve key generation | | @@ -416,7 +451,7 @@ knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ | (subject: reserve.pub, amount: value) | | | | +------------------------------+ - | | persist (reserve.pub, value) | + | | Persist (reserve.pub, value) | | +------------------------------+ | | +-----------------------------------+ | @@ -442,7 +477,7 @@ knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ (W1) reserve key generation (wallet) reserve = EdDSA-Keygen() -persist (reserve, value) +Persist (reserve, value) ]]></artwork> <t>The wallet derives coins and blinding secrets using a HKDF from a single seed per withdrawal operation, together with an integer index. @@ -453,18 +488,18 @@ and might be chosen to be implemented differently.</t> (W2) coin generation and blinding (wallet) batch_seed = random(256) -persist batch_seed +Persist batch_seed for i in 0..n: coin_seedᵢ = HKDF(uint32(i), batch_seed, "taler-withdrawal-coin-derivation", 64) blind_secretᵢ = coin_seedᵢ[32:] coinᵢ.priv = coin_seedᵢ[:32] coinᵢ.pub = EdDSA-GetPub(coinᵢ.priv) - h_denomᵢ = SHA-512(uint32(0) | uint32(1) | denomᵢ.pub) + h_denomᵢ = Hash-Denom(denomᵢ) planchetᵢ = RSA-FDH-Blind(SHA-512(coinᵢ.pub), blind_secretᵢ, denomᵢ.pub) - h_planchetᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) + h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) planchets = (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, - ( sum( ⟨denomᵢ.value⟩ ) | sum( ⟨denomᵢ.fee_withdraw⟩ ) + ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) sig = EdDSA-Sign(reserve.priv, msg) ]]></artwork> @@ -473,30 +508,29 @@ sig = EdDSA-Sign(reserve.priv, msg) (⟨h_denomᵢ⟩, ⟨planchetᵢ⟩) = planchets for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) - check denomᵢ known and not withdrawal-expired - h_planchetᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetᵢ ) + denomᵢ = Lookup by h_denomᵢ + Check denomᵢ known and not withdrawal-expired + h_planchetᵢ = Hash-Planchet(planchetᵢ, denomᵢ) msg = Gen-Msg(WALLET_RESERVE_WITHDRAW, - ( sum( ⟨denomᵢ.value⟩ ) | sum( ⟨denomᵢ.fee_withdraw⟩ ) + ( Sum ⟨denomᵢ.value⟩ | Sum ⟨denomᵢ.fee_withdraw⟩ | SHA-512( ⟨h_planchetᵢ⟩ ) | uint256(0x0) | uint32(0x0) | uint32(0x0) )) -check EdDSA-Verify(reserve.pub, msg, sig) -check reserve KYC status ok or not needed -total = sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) -check reserve.balance >= total -persist reserve.balance -= total +Check EdDSA-Verify(reserve.pub, msg, sig) +Check reserve KYC status ok or not needed +total = Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ +Check-Subtract(reserve.balance, total) for i in 0..n: blind_sigᵢ = RSA-FDH-Sign(planchetᵢ, denomᵢ.priv) -persist withdrawal // todo: what exactly? should be checked first for replay? +Persist withdrawal // todo: what exactly? should be checked first for replay? ]]></artwork> <artwork><![CDATA[ (W3) coin unblinding (wallet) for i in 0..n: coinᵢ.sig = RSA-FDH-Unblind(blind_sigᵢ, blind_secretᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) coinᵢ.h_denom = h_denomᵢ coinᵢ.blind_secret = blind_secretᵢ // todo: why save blind_secret, if batch_seed already persisted? -persist ⟨coinᵢ⟩ +Persist ⟨coinᵢ⟩ ]]></artwork> </section> <section anchor="payment"> @@ -508,8 +542,8 @@ where the sum of all contributions (<tt>contributionᵢ &lt;= denomᵢ.value</tt The payment is complete as soon as the merchant successfully redeems the deposit authorizations at the exchange (cf. <xref target="deposit"/>).</t> <artwork><![CDATA[ wallet merchant -knows ⟨coinᵢ⟩ knows merchant.priv - knows exchange, payto +Knows ⟨coinᵢ⟩ Knows merchant.priv + Knows exchange, payto | | | +-----------------------+ | | (M1) order generation | @@ -555,13 +589,13 @@ knows ⟨coinᵢ⟩ knows merchant.priv wire_salt = random(128) determine id, price, info, token? -persist order = (id, price, info, token?, wire_salt) +Persist order = (id, price, info, token?, wire_salt) ]]></artwork> <artwork><![CDATA[ (W1) nonce generation (wallet) nonce = EdDSA-Keygen() -persist nonce.priv +Persist nonce.priv ]]></artwork> <t>Note that the private key of <tt>nonce</tt> is currently not used anywhere in the protocol. However, it could be used in the future to prove ownership of an order transaction, @@ -570,12 +604,12 @@ or proving the payment without resorting to the individual coins.</t> <artwork><![CDATA[ (M2) contract generation (merchant) -check order.token? == token? +Check order.token? == token? h_wire = HKDF(wire_salt, payto, "merchant-wire-signature", 64) determine timestamp, refund_deadline, wire_deadline contract = (order.{id,price,info,token?}, exchange, h_wire, timestamp, refund_deadline, wire_deadline) contract.nonce = nonce.pub -persist contract +Persist contract h_contract = SHA-512(canonicalJSON(contract)) msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) sig = EdDSA-Sign(merchant.priv, msg) @@ -585,8 +619,8 @@ sig = EdDSA-Sign(merchant.priv, msg) h_contract = SHA-512(canonicalJSON(contract)) msg = Gen-Msg(MERCHANT_CONTRACT, h_contract) -check EdDSA-Verify(merchant.pub, msg, sig) -check contract.nonce == nonce +Check EdDSA-Verify(merchant.pub, msg, sig) +Check contract.nonce == nonce // TODO: double-check extra hash check? // todo: maybe get rid of CoinSelection altogether by claiming we already know coinᵢ and contributionᵢ ⟨selectionᵢ⟩ = CoinSelection(contract.{exchange,price}) TODO: include MarkDirty here @@ -600,15 +634,15 @@ for i in 0..n: | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) sigᵢ = EdDSA-Sign(coinᵢ.priv, msgᵢ) depositᵢ = (coinᵢ.{pub,sig,h_denom}, contributionᵢ, sigᵢ) -persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) +Persist (contract, ⟨sigᵢ⟩, ⟨depositᵢ⟩) ]]></artwork> <t>// TODO: explain CoinSelection</t> <t>// TODO: maybe introduce symbol for pub/priv</t> <artwork><![CDATA[ (M3) deposit check (merchant) -check sum( ⟨depositᵢ.contribution⟩ ) == contract.price -check Deposit(⟨depositᵢ⟩) +Check Sum ⟨depositᵢ.contribution⟩ == contract.price +Check Deposit(⟨depositᵢ⟩) msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) sig = EdDSA-Sign(merchant.priv, msg) ]]></artwork> @@ -616,7 +650,7 @@ sig = EdDSA-Sign(merchant.priv, msg) (W3) payment verification (wallet) msg = Gen-Msg(MERCHANT_PAYMENT_OK, h_contract) -check EdDSA-Verify(merchant.pub, msg, sig) +Check EdDSA-Verify(merchant.pub, msg, sig) ]]></artwork> </section> <section anchor="deposit"> @@ -625,10 +659,10 @@ check EdDSA-Verify(merchant.pub, msg, sig) <t>Deposit could also be used directly by a wallet with its own payto and a minimal contract.</t> <artwork><![CDATA[ merchant exchange -knows exchange.pub knows exchange.priv -knows merchant.priv knows ⟨denomᵢ⟩ -knows payto, wire_salt | -knows contract, ⟨depositᵢ⟩ | +Knows exchange.pub Knows exchange.priv +Knows merchant.priv Knows ⟨denomᵢ⟩ +Knows payto, wire_salt | +Knows contract, ⟨depositᵢ⟩ | | | +--------------------------+ | | (M1) deposit preparation | | @@ -665,8 +699,8 @@ sig = EdDSA-Sign(merchant.priv, msg) h_wire = HKDF(info.wire.wire_salt, info.wire.payto, "merchant-wire-signature", 64) for i in 0..n: coinᵢ = depositᵢ.coin - denomᵢ = Denom-Lookup(coinᵢ.h_denom) - check denomᵢ known and not deposit-expired // todo: check could be included in Denom-Lookup + denomᵢ = Lookup by coinᵢ.h_denom + Check denomᵢ known and not deposit-expired // todo: check could be included in Denom-Lookup totalᵢ = depositᵢ.contribution + denomᵢ.fee_deposit msgᵢ = Gen-Msg(WALLET_COIN_DEPOSIT, ( h_contract | uint256(0x0) @@ -674,18 +708,17 @@ for i in 0..n: | info.time.timestamp | info.time.refund_deadline | totalᵢ | denomᵢ.fee_deposit | merchant.pub | uint512(0x0) )) - check EdDSA-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) - check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) - check coinᵢ.value >= totalᵢ - persist coinᵢ.value -= totalᵢ -persist deposit-record + Check EdDSA-Verify(coinᵢ.pub, msgᵢ, depositᵢ.sig) + Check RSA-FDH-Verify(SHA-512(coinᵢ.pub), coinᵢ.sig, denomᵢ.pub) + Check-Subtract(coinᵢ.value, total) +Persist deposit-record schedule bank transfer to payto timestamp = now() msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, ( h_contract | h_wire | uint512(0x0) | timestamp | info.time.wire_deadline | info.time.refund_deadline - | sum( ⟨depositᵢ.contribution⟩ ) + | Sum ⟨depositᵢ.contribution⟩ | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) sig = EdDSA-Sign(exchange.priv, msg) ]]></artwork> @@ -697,18 +730,19 @@ msg = Gen-Msg(EXCHANGE_CONFIRM_DEPOSIT, ( h_contract | h_wire | uint512(0x0) | timestamp | contract.wire_deadline | contract.refund_deadline - | sum( ⟨depositᵢ.contribution⟩ ) + | Sum ⟨depositᵢ.contribution⟩ | SHA-512( ⟨depositᵢ.sig⟩ ) | merchant.pub )) -check EdDSA-Verify(exchange.pub, msg, sig) +Check EdDSA-Verify(exchange.pub, msg, sig) ]]></artwork> </section> <section anchor="refresh"> <name>Refresh</name> <t>// todo: add introductory text</t> + <t>// todo: factor out Derive (from shared secret onwards)</t> <artwork><![CDATA[ wallet exchange -knows ⟨denomᵢ⟩ knows ⟨denomᵢ.priv⟩ -knows coin | +Knows ⟨denomᵢ⟩ Knows ⟨denomᵢ.priv⟩ +Knows coin | | | +-------------------+ | | (W1) coin melting | | @@ -759,52 +793,52 @@ for k in 0..κ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) for i in 0..n: - h_denomᵢ = SHA-512(uint32(0) | uint32(0x1) | denomᵢ.pub) + h_denomᵢ = Hash-Denom(denomᵢ) planchets = (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) sig = EdDSA-Sign(coin.priv, msg) -persist (coin.denom.pub, ...) // todo: double-check +Persist (coin.denom.pub, ...) // todo: double-check ]]></artwork> <artwork><![CDATA[ (E1) gamma selection and coin signing (exchange) -denom = Denom-Lookup(coin.h_denom) -check denom known and not refresh-expired -check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) -check coin.pub known and dirty +denom = Lookup by coin.h_denom +Check denom known and not refresh-expired +Check RSA-FDH-Verify(SHA-512(coin.pub), coin.sig, denom.pub) +Check coin.pub known and dirty (⟨h_denomᵢ⟩, ⟨planchetₖᵢ⟩, ⟨transferₖᵢ.pub⟩)) = planchets for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) - check denomᵢ known and not withdraw-expired -value' = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) -check value' == value -check coin.value >= value -persist coin.value -= value + denomᵢ = Lookup by h_denomᵢ + Check denomᵢ known and not withdraw-expired +value' = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ +Check value' == value +Check-Subtract(coin.value, value) for k in 0..κ: for i in 0..n: - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) -check EdDSA-Verify(coin.pub, msg, sig) -(ɣ, _, _, done, _) = lookup refresh-record(commitment) -if refresh-record not found: +Check EdDSA-Verify(coin.pub, msg, sig) +refresh_record = Lookup by commitment +(ɣ, _, _, done, _) = refresh_record +if refresh_record not found: ɣ = 0..κ at random for i in 0..n: blind_sigᵢ = RSA-FDH-Sign(planchetᵧᵢ, denomᵧᵢ.priv) link_info = (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) - persist refresh-record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) + Persist refresh_record = (commitment, ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, false, link_info) msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, ( commitment | uint32(ɣ) )) sig = EdDSA-Sign(exchange.priv, msg) @@ -812,19 +846,20 @@ sig = EdDSA-Sign(exchange.priv, msg) <artwork><![CDATA[ (W2) secret revelation (wallet) -check exchange.pub known +Check exchange.pub known msg = Gen-Msg(EXCHANGE_CONFIRM_MELT, ( commitment | ɣ )) -check EdDSA-Verify(exchange.pub, msg, sig) -persist refresh-challenge // what exactly? +Check EdDSA-Verify(exchange.pub, msg, sig) +Persist refresh-challenge // what exactly? for k in 0..κ and k != ɣ: revealed_seedₖ = batch_seedₖ ]]></artwork> <artwork><![CDATA[ (E2) commitment validation (exchange) -(ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = lookup refresh-record(commitment) -check not done // todo: sure? +refresh_record = Lookup by commitment +(ɣ, ⟨blind_sigᵢ⟩, h_planchetsᵧ, done, _) = refresh_record +Check not done // todo: sure? for k in 0..κ and k != ɣ: ⟨transferₖᵢ.priv⟩ = HKDF("refresh-transfer-private-keys", batch_seedₖ, "", n*32) for i in 0..n: @@ -835,29 +870,29 @@ for k in 0..κ and k != ɣ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment' = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) -check commitment == commitment' -persist refresh-record = (_, _, _, true, _) +Check commitment == commitment' +Persist refresh_record = (_, _, _, true, _) ]]></artwork> <artwork><![CDATA[ (W3) coin unblinding (wallet) for i in 0..n: coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) coinᵧᵢ.h_denom = h_denomᵢ - persist ⟨coinᵧᵢ⟩ + Persist ⟨coinᵧᵢ⟩ ]]></artwork> <section anchor="link"> <name>Link</name> <t>// todo: add introductory text</t> <artwork><![CDATA[ wallet exchange -knows coin knows refresh-record for coin +Knows coin Knows refresh_record for coin | | +----------------------+ | | (W1) history request | | @@ -887,7 +922,8 @@ sig = EdDSA-Sign(coin.priv, msg) <artwork><![CDATA[ (E1) refresh secret lookup (exchange) -(ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = lookup refresh-record(coin.pub) +refresh_record = Lookup by coin.pub +(ɣ, ⟨blind_sigᵢ⟩, _, done, link_info) = refresh_record if done: melt_info = (ɣ, link_info, ⟨blind_sigᵢ⟩) else: @@ -900,7 +936,7 @@ else: (refresh_seed, ⟨transferₖᵢ.pub⟩, ⟨h_denomᵢ⟩, coin_sig) = link_info for i in 0..n: - denomᵢ = Denom-Lookup(h_denomᵢ) + denomᵢ = Lookup by h_denomᵢ for k in 0..κ: for i in 0..n: sharedₖᵢ = ECDH-EdDSA(coin.priv, transferₖᵢ.pub) @@ -909,22 +945,22 @@ for k in 0..κ: coinₖᵢ.priv = HKDF("coin", planchet_seedₖᵢ, "", 32) coinₖᵢ.pub = EdDSA-GetPub(coinₖᵢ.priv) planchetₖᵢ = RSA-FDH-Blind(SHA-512(coinₖᵢ.pub), blind_secretₖᵢ, denomᵢ.pub) - h_planchetₖᵢ = SHA-512( SHA-512( denomᵢ.pub ) | uint32(0x1) | planchetₖᵢ ) + h_planchetₖᵢ = Hash-Planchet(planchetₖᵢ, denomᵢ) h_planchetsₖ = SHA-512( ⟨h_planchetₖᵢ⟩ ) -value = coin.denom.fee_refresh + sum( ⟨denomᵢ.value⟩ ) + sum( ⟨denomᵢ.fee_withdraw⟩ ) +value = coin.denom.fee_refresh + Sum ⟨denomᵢ.value⟩ + Sum ⟨denomᵢ.fee_withdraw⟩ commitment = SHA-512( refresh_seed | uint256(0x0) | coin.pub | value | SHA-512( ⟨h_planchetsₖ⟩ ) ) msg = Gen-Msg(WALLET_COIN_MELT, ( commitment | coin.h_denom | uint256(0x0) | value | denom.fee_refresh )) -check EdDSA-Verify(coin.pub, msg, sig) +Check EdDSA-Verify(coin.pub, msg, sig) if ⟨blind_sigᵢ⟩ returned: for i in 0..n: coinᵧᵢ.sig = RSA-FDH-Unblind(blind_sigᵧᵢ, blind_secretᵧᵢ, denomᵢ.pub) - check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) + Check RSA-FDH-Verify(SHA-512(coinᵧᵢ.pub), coinᵧᵢ.sig, denomᵢ.pub) coinᵧᵢ.h_denom = h_denomᵢ - persist ⟨coinᵧᵢ⟩ + Persist ⟨coinᵧᵢ⟩ ]]></artwork> </section> </section> @@ -1032,7 +1068,7 @@ if ⟨blind_sigᵢ⟩ returned: <refcontent>National Institute of Standards and Technology (U.S.)</refcontent> </reference> </references> - <?line 1061?> + <?line 1079?> <section anchor="change-log"> <name>Change log</name> @@ -1045,190 +1081,199 @@ Education and Research (BMBF) within the project Concrete Contracts.</t> </section> </back> <!-- ##markdown-source: -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 -J5Qtjh9op3jSsP4XWH0PNJSKAAA= +H4sIAAAAAAAAA+09y3bbVpJ7fMWNvAiYECQlP5JowmRkSbbUtmS3JLc743hI +kLgkMQIBNh6SGFu9mI+YbZ8zvfDMB/Smz+xm1Zv+h/SXTFXdBy5A8CFLsp0z +4fGxQOA+6ta76tYFHcexzjbZXctK/TTgm2ztZMTZ48MX7MQNeMyex1Ea9aNg +zfKifuiOoYUXu4PUGWY8Tfqj6NxJsaEzkQ2tvpvyYRRPN5kfDiLL8ifxJkvj +LEk3Wq1vWhvWeRSfDuMom2ALj084/BemVpLG3B0X753yKbT2Ni3GHEbz0FU/ +nk7SaBi7k9GUbvC+m4zoauJOx9Azsaw7ZzzM+KZ1h7GYT6JNNkrTSbLZbA79 +tDEMs5CnjSgeNoPEawFgDbjdxMYBwJ+keXN4XtG8aVlulo6iGGBzYGbGBHIO +/NMocH32+H//R6CHnkHHTXbyYoftxDyBlbEXoX/G48RPpywasBPeH4VREA2n +1Nrt9WJ+hh1Ue7qNCOIA2B4PxqMoSH+CGw223qKHfRhqs9C8H3kAz47TWm89 ++EbeycIUCfOYx2M3FJPxsesHm2ws4G5osv5zmjmeGK7hccsKI+iTAtRIjKNH +2xstdbHeuicv73/94Bt5+WDjLt3de7LzCKB4tt9Yb8G/1lfNb7762rnrPLi3 +4azfg1bOV52796Dh8d6xbvegtfF183D/+KTxaP/5cWP965ZzDxgJ2EnDYFmO +4wCmAAduP7WsH1+xk4cv2Y+vxYOx73kBQH2H7cOSIy/rp34UFpo95OduzFk6 +clP4z08YMHiGvMPgOkn9IGDIqY4fIm8PARUJc0OPjd0pYDJMXT9kPI6jOGlY +LxLOYJhplMUsOg9Z7Cenn+Hsh1Hqipkd1l1ze/21LgOMRsBiMClngZ/y2A2Q +tn44ZF1o0WU8RNp5zE3Y1vH2/j57Rfh+jWO47C3rFccAYFDmQpoI2cll5346 +Yj1sH/DQdmulSXk4hOcAf2+K96AL3sYvChAX+05c7194HNnTOisPUWhbZz9B +MweaI9RpZE4CY0/FNDhiz08T+6LWbXbpFl4Whh37oT8GdITZuAfKBzpjj6YA +M+R9oIEbT3EKEGmgCFKL+mVB6k8CzuBm308QD34IagjGuMB5M/iGyyjP1512 +AVA3SVniD0N/4PddGBHnVEhRw3QvusVlIisoQgEmQT0gtwjEgMqCLnbPH0IT +z3fDmgLiB1ry+YgD53V/6CKruWwSgSIArtaTyeXDQ/6HzD8DvYfrjOQ6fqB1 +4IgxABGN7amxKpeJm8GUDXkIvJUCfAkMA7BySQ1YnmClL1iP2ePIY4cwQpPu +FG4VSCNQDBgiNmsyfjGJQgDM13w3nwoIFyKsV2cwdhZE7BAhAHkGrHb9Afx/ +5sa+24POboK0IEHstuEB9gMW93ycBngjmuCqIhwTxTdiPYG4GGbFxQb+KUo1 +98F4TUFDh83fZIHvGqik1Xtu6jbAvhRXCZIOw4NW7tIjWAHejXr/xvup6EOI +bzUaYbEjv+gHWYJEBPwPCdNFaiZsEEdj6NolSobOOo30jz/9F47681/+8x9/ ++u8uCS5wk0sahl+kOE7XZ21WnLEL1wKoREAF/QFVWigET475GOwwqhExnh9m +UYZoi91pcW5ExFXmd0MxipIShEeizkfwBPJAE4ESrYQVdeN2bsX9Pjga/pik +AE13+eEemHf2KAtJi1ODO2AwtpyN+w/YmzvJyIWLS8v64x//aMnb9jgZ1pjz +HRuhZ2Dth5Ms3SSDBw/wDyAIbgGOQKUIekmN9ZR9yzb+9cE6i/opR0fiWZbq +zjgaDSK7ef4QnAXsPfAvkPnEGAjvUzDzbXZ3Q42DwFldHIDEnviKRsbeajFA +K0AiO+ZipexeY73O7uN/D/A/lIUHjQ3s8Uoa2deNHB331zcEOuDCQAd8uy46 +1jfu3ww+HtxbGR+4mll8bCA+NhAfdxU+7i3AB2HVBveTbKSnbtc0nhx6VsTW +TTDQjWGsxEEnOZ4k8vpRDFIPythLlPEd+DGMqXsqMZ3Fr8cHfii0xZs3knEu +NwUyUj6ewPwmB1kEfZvho1etzbvrrwVQ4OaYnpRQ0aj/PX8w0LpPjtSUjG7M +/Qq8v9d4IUwu6AFQ9DQXaOyMA0lRJxxIlG2B442GR5qibbDBUifsHWxtA2VH +Y1dRFO84iEwbdFKdoU4jqgIqgC4THEDQZU/SpV9QPATDQCoe4VZJJBbpVGAQ +mIkYJOF9MEn0NecON5Ueh+qJLUnVapZCJUnaMwZTjf6O6FrkIwCDyb+SpiWI +hHzBYxKvvhv0s4BEoIR66cK/lkh+AtDu8BhMJS25pHTRnUcEn3qDS8GLOB2M +NKcbs7FHjWWJmE0ElT5ZED9MUtf0H17JEOK1RYgGh0ryD/oCBUoAQ0UxMcvu +BQUApAl2Lyb4J0n5BBeZZEMULblMhOP1ptXFv47s1UW4wCoRkwB3AnOCFT0f ++eCGqIY4ZLkdMHCX2f1BA4SGeO2y1pDsBp3sxA3SOtt/clCnELjOnhLLPXty +INkkESTEdkTCiXRt6AZxPLNdFkahI1lI+HTiUe2fqHP+8QfQNEX7e+aDNwqT +ihCGk8/oKjcdEAzKF11YCFiwHwBo6DHgUmwF8RVQ0Q0s8WQQFQBUXgGi2Z3k +3mAyAYcPvGemY7QoLEFpg2uNrporvGjJrAK2GrV9KlvmkYPk7RJoSE6pEUtT +fNtmG/fvfwF6r82+Xn/QqhVF5plc8LxxbZjyqRy6NqNtsfeMIA2iIIjOE6kx +nx89gZlNDst5oSa0x55QoEr/2qZlrFk4hx4AOc+GIQ0uKg9Bxs00FRJsJarO +QeRBpOui9ElfliS4oPZJmC/rQnVrXQdsl8UhCSpCRcoduQrCMxRh+I5u4hAc +tnBR+HVoCAZCYx/WWUk+qoXjUFF1/uAFIfrYUrSCGH0AOboavxeIeWiwPHAH +RlrSzYBxyIZ4EF/FY2IcdeknaIYDiDZ7islckWdCNS8Muf8TykpK8EpuUC0g +rrC8iMEXPyBwv2WHAvYLKQdlZcreMgyC1x/YcoxaXaQX7MNaTa+6TY1sSjcc +1jBezhNgMO2XbbauBIUdAoM8DHygxDHEnS5wvfImdj3QJuvfUKvqFkfHW86j +nT2wiHHiOgNvdEn3wfvMJpMIgiFAt2FBcUrZBd2pOptkPSAKCQB0LgqAcjOl +o0j3RHuG8+I18A15F8BOCVJC8CdF2BDnHRJ7yWYqWme8yCMwLc0yyILAAenA +rBaZWhwIIIjOAF9i2sah9CmgT9mn0NRXci48PqFcHIDo8nJWXRI922xNoGSL +PTqZJC8/W7NIgNuKzoK6CoRaLeeAwhMuyQ+PVdv8klu4zraGTg+nlBFRg5RR +LgRAZVQ9mJajFUvBI1cG/Wx0292hi24MMD2Ikt/3Mb6WxIHxrd6UAf78Acmd +9I85G8accsxAt/EYnWSIqkGWMKnB7GHfq1HUTXMiBbsKWEL6eqPARw45Xdzu +nSYFdiL/1mQnaEDo6SEjIzjIOFIxmrGLGXDcDsspz7WK5RDKKparcGOvxXIP +FRagr2a4wl0Anyw40ISiCoBmbRkXEREMLiJVoAYtko3uCiWwlHRVmuDTI6cK +aAkioAx66FIMltNQKVJJQqVNKyhIwVGbValRK84fVMmFFXc4tIgxy6kUg0h2 +au1i4WqgCTT8QsRhpec5XdEWzJAVb9rYDyaF+RVJE39YJCkNrf6KHCZmPdHv +cHPnqWCf5XggEkRH8U3QkcIuvpyQsp2mpFekJEDA5F9h5ZAyBCHoMTmhpCU0 +qQgri5TC4dqiPyJcgu9JjKrVmCh9EfYqhUXet2HEFcRFLYP65Iv5RMUmC5XA +5JAuCt2LOF7O8X54Bm3gfx4nlKWKq1kesfYFE+3n8vzvlDEr0kfcFrJINLqq +NqvgvIHCOZkD6HTLdEjjjNcxCkBYaFsG4gR/JarcsBYDSBU9lukpW4zTxtUZ +od924IKX4hzjrnIcY659B9ql0wkXW5j5riQ03hqjV5yAD+uKK/Bh1T3cYSlt +KogMDuZl1a4V5S2dSQRaS8RWiVWkR5fudrETuj8YkYV86Ba2vCZunOqNSMAn +62dxzMP+tG7RBsMAbTGA3JUhJ0CShXKfDujKRlnoxQDh2A8CaAZCbK9z5+va +nDHJr1JfNWR3nfV1ueXaH7k4I+YufSxF0D5cPgo4Yi9HPOTInyq/JfeQqBKi +LiYGxy6e5ljMd2lD1hUYR36yaG/vwT1b3GqIIFX6unc31G2FBnyitmfXN8Bo +iMcKtFqXsuDsxAcpS93xBIKPrV4SBRnmafXNGfoCHhQcxj7lRTnHbIm92j4E +wbxP9xIftxe769981XJa6/CPtVqbrRbb3j0+6TIb27843P8945OoP6o1yL+m +YBfCTBGqd1sXj0qfbg5bwtYI0WsN69vPHMdqNgEML9oEOvH+KQXvnKttWQK2 +n1pHPBBM9j4rBhHXnGYu9b1gB19IQu843xFlzEhyKwiUNkwKzkAubwA9yAel +f1w24i7uL8siBMmWPjSCSBsRYLlyLyERLopON4DDGoPYAl/BsmM+BBHlKDRg +3h9vHW5JBrZePT58EYJB1DCy56Jf8trWRTRu6JplMeLS0doykXc6+Z3GKB0H +YJJo3njaEEtzYi60qIQtkWuFWGm91WrJaOcxD52DZKgWQAFbtXmRLQxTou4U +1wzj45qrTJLAF2gzG3d1GxLftQJpqo0IBHSTgIOMqZFskIt5IxTtCeZ7qiy8 +UPRSC2A1B67cUAyapG/JSKqsxh4PcOPMSD6cEGsj/7usz2OqX8nrLPLM+vnI +74+oXTQALEjBEiozxTGQmRkErCKYBauCGaA8cZhMgS0vcMwsQeYDzQ64HIEo +4JIc1n0SwurIL+yqhBeXW8NdnWc8DbGOxkU/MYp9JiNnwRpSpSs9m2834ejb +pBBQVLsi9vZph14O0IuigIPm1QUFDKIUXXrQFd1wNxK9AQsLt5jbi+I0mTMj +ixAl537Cce7nWNKVpHJtE/FNdBW6RC1SlshEiDi8hdZJFSUAtZ9G0Wk2QbGk +jXRwTmMf9EeCKdAzzDEEUzU6sIwcFFobE2FHHPE4G7NSjQEsT7g2GKEBBwCS +EI6ZPXqGvJuRlyw9A7XnoRyFyxriCBnDLIXIh0xgckGsRBVRACMeR2MQEGSi +nOmIQMhzIrsyiqNsOELWf3NHIV1tS2K6G3zdMBrblDipsTaVj4ldSikVLUNC +1vGamjbAf6pZYoTngRuC8Ujtibyos4rh8gs9ADOGbl3Q4GoIBoMT/znHWU/k +/8lAgFdM38Fb8MT4gkuF9fiubTyGZ4qLxFOn8FSIN0MTJAyDqJPQZZoCX334 +ElAqkr0Ek+HF7rmLj871F7l5d47xZaorhbCwhH3HWmjvfaAKVojgleIcpHSM +tUTI1VjXkSt3akuRDGp81YEcYVkeM8K6GFAjKTBzLyPfUKxQSoOY0u2D9Sae +oyoZgGQE2s3IkRG7UOkK3pETCaOcglurTDKCmi+XDTjoYrXrISVFwUQVBub4 +NWucAf5RU8sAHAQFlg3jSFiFAIA6iuLc70NtF1DakMdnkpNRxHIwwJhpx0VX +G55sPd096hxs/b6z/Wz/8FjUSCBIuZKxPYGm7WOHSs5Eql8PBaady+WKrBUD +8Qk9wPOEANh+eCxEx9xHkKRf+lFYsoTaLiJ+freZ1g2M+KFLaTsDpGfFz1vr +S2fR58sFPd8y+yVIqiINhoyS5xG980G4zpzvv87yDWNCZvfc8BTMkxsmA/Qo +jGffVcxpg+5Atbup1o4aTIULm3Ir7GahrW63GI/OlysO81ZrR7uwIBUufVho +KuBbwjDz2UYw6UaN9KDJnVQ3qdJVVYBcZ873X2f5RnE21lRajxUfVDEpfIrU +VPY0oXRS7RagrW63FI+rs6m9uy5J6SdJ5mJ07IZ5RPbBIaqAcabntwb97Flz +XiuS8jrkmLOs+epU90QhuSsxm4UL5eLG5izfWNYj70numsgp2GiJj4636mSn +0bWF+BCCX9y5FykdYaMXGSpbGG1oqRq02a63c7zlPOFTaGfXrLJ6VKox39aU +ht+jbHGi3C5TyYh8eCLjN1eUjJAr5yonJ+EYsXPTucn9lbqVRkOOgZHMWOQ7 +GpjpvkBvDYIQOmaBi4doBuJA95Rmg7YYQ491qszjEKoGMsNEfhbO7eN5AMy8 +YWIFPGHaknXDqQjIKJ0o03xjfzgiZ056krJiXE2CxQyqyCyYmh6aZH9CRhPR +RC6M2dooscdMDTlyu7/f3ts6fLzbgag87Sj0dChybUL8huFwh5AOAUeUYdgp +cliEKkEcp4/RkQ2efA/i0ukcjCBbaaZZZjhy1um5aX/UISS2ZUmMvXH/Qc46 +eQMLedbHtWH5N6Y6cA56BCpBVWjIWMjHSgzdtc7WxLmwnEEc7Ox4ukhwrU41 +T6yAaDGuOc2ruxubr+XUyo8sN9m8u1FoAlGakozHPH2e9WyzN8456ijHFJdR +CinhLrZRZkg0Ku7cqrjQmBLXX1yJDCnVc5q2OGh1KFroWrO0OcSMP+jlHHbQ +ynV0so2OqKgtTGi1mUqavdx6+nT3pHO0e7x79Lvdzsv9k72do62XdVJsEOLK +FIEClRQGOvdvZx5BGKVZWvnyb/MYmWArwqKjZSypbl20CsHzzLdaTe4aCtrR +bqr2DIByIu8ndBnx/WIra6vgBdh+JcTBzBrZs8xvcEyeocnH1GG9bieTWAAR +lpYZksAvJj5Eju/LEL9g8goMCfLKbcOC66f2EFVLZeie/LCN2b80S1h0ivE4 +YlSkJi0R97fnr/TLpSstZW0USD0XVwtGlKaozbKE6SIZOoIYt5J4UgEpXWuY +T213zjEXxi9cNIzfswR8hcAT9gtgxJwCFfcjKGBNAnf6vSEPVb5Rrvor1TkC +JYSuvN1uLm4F3SYIVtoTrtaTxryz46iHUrIAsIKMqccmPNCmbERMfIJ/4Z7x +QhPa6jVMoRvE3PWMtOr3mkaFJJhOsD8XB40x4Sauiim1qIcJ9oRSyrLW3Kjp +JOrhBiCdFDTyZGMeo8ZKLXeAhYr9wPXHSENfb/50wwg4stuwFADqbAedOfQp +R4w+BqW5tCJEf8gHDgZk074tE4eX/Z8KCTV6ZB5Fm5MHhCnVDsLivFxdOr/k +tolkMB4JI5xQEhA72V3zO9Lu2zYrCnG3xiglN0aCiRNnCq8oUIAP0NsZ7kmn +dCIx0OsEQZdbdpJMtHevtmnwlECErlJSwD7A2sezplihhlu3Hudj0WIO+uTu +gs4oyjy5bJ0fDzCjg1UTcJoldEotJ8WCbqK16kxqpxzILOmrc7aIujT6aNH3 +FYLuA3AHhCCY6b0PNH8FRHNDbGb/9sjBg0usyQ4fbcP/L472jQB7Ecw2rbDx +xvfqaXTKw+8va2bDW8hdLYuRdYKVdNNqqdXrz1m+saTDgp6KKE3CbNJ8IzDs +e5dN0sCSLnNyV/CxaeHCgxF9BWU0YT5q7uoqAkRxpNCsNylDtyFGoGUlpHVD +zyEJKGm4TIyWzbloKYt7ykSuMjjCIK8gD9eZs3xjxWVeTRhgRVpFzRcG8bHL +HkWt3PzjiMRVhOFu7i+JHM0HmbkClkWpWkEtW/N84XNLArCQG3WSVgmAKMPo +L5eA68z5/us0k7RGbpYZuVlwgaLA70/FtrJMoGKtBp5bUDm4SufDVpoJWp1D +zN+RZw1k4m194+uapc9VMTDpjPxZdeZPmBEdiojh28ye0xCTy3IOM0tSaZrz +sFA8mptKlrYNHciZw968UHyOtZ4iPCEvm4oRMU2KsTpth7vhVCC6VCrZsPai +c0zn0rG7vop4zarKQUbFXGlER/Q4vmYH4Bv5E1lLKVBDG6OiRLJu8dDFkG+I +4zh9l2rLMqxtSthaFqrYag2zCWpDNZY5aDkappRlQhkmwzHxUAMeEZS1oIrF +Fd8Az8hjX7JSAGPwM9/L6NAfxFINxSxzDK3JLyKgNv0JLPWVLDHqIKlVDlaT +XTrrdbamBnLwWV6YJ7OuOc/p4kh8f8gggwjZg0gY8MYlN6mvloa3bfqggg2J +C6U7WjdCBwFmffVZanqahuJL7VxpllRNAAsGUDrXAEQLsQDpN8fPDrWTUCvn +zQ52jzBVf9LZfnZ4crS1fYLQ6tazichCLDWTiZxn8XMxu0VYK7JqRY+onFYr +41giGTc/Tp7tPNuUWxOOMHuiqITOaNGN7/NNkrE77aFaAdb3PRTFbeDyYx6I +F3ZAtK+3gnrTPJ1xznXCBXOkKqej3vVjJgMs8CMSNZ6MetvFWTTaGm804xFX +QmAkViMq2Tg7cOPTHR/f74NaaDYfptJUeVKqXoYHc8QmPBaVj4r0Xykji/U8 +nZ3d58+O90/q0kDZBt1KqVNLWS68ibwhM6h6dVLk35YzZLqjbqmlzbxZkrti +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 -->