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:
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ᵢ <= 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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
-->