commit ba33758e6f7ab5020785634ad4ba026c89443c9c
parent 78f571ddd833bb028b78ccac48ea0be773f61236
Author: Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
Date: Fri, 24 Jan 2025 19:42:13 +0100
protocol: withdrawal
Diffstat:
2 files changed, 235 insertions(+), 72 deletions(-)
diff --git a/draft-guetschow-taler-protocol.md b/draft-guetschow-taler-protocol.md
@@ -14,9 +14,9 @@ keyword:
- ecash
- payments
-# venue:
-# repo: https://git.gnunet.org/lsd0009.git/
-# latest: https://lsd.gnunet.org/lsd0009/
+#venue:
+# repo: https://git.gnunet.org/lsd0009.git/
+# latest: https://lsd.gnunet.org/lsd0009/
author:
-
@@ -60,6 +60,7 @@ Use at your own risk!
- `bits(x)` denotes the minimal number of bits necessary to represent the multiple precision integer x
- `bytes(x)` denotes the minimal number of bytes necessary to represent the multiple precision integer x
- `bigEndian(16, x)` denotes the 16 least significant bits of the integer x encoded in network byte order (big-endian)
+- `random(256)` denotes a randomly generated sequence of 256 bits
- `a * b (mod N)` denotes the multiplication, `a ** b (mod N)` the exponentiation of a and b, modulo N
# Cryptographic Primitives
@@ -234,7 +235,7 @@ by verifying that the greatest common denominator (gcd) of `fdh` and `pubkey.N`
RSA-FDH-Derive(bks, pubkey) -> out
Inputs:
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -256,7 +257,7 @@ RSA-FDH-Blind(msg, bks, pubkey) -> out
Inputs:
msg message
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -298,7 +299,7 @@ RSA-FDH-Unblind(sig, bks, pubkey) -> out
Inputs:
sig blind signature
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -341,6 +342,76 @@ out = (data == exp)
## Withdrawal
+The wallet creates `n > 0` coins and requests `n` signatures from the Exchange,
+giving values to the coins according to `n` denominations.
+The total value and withdrawal fee (defined by the Exchange per denomination)
+must be smaller or equal to the amount stored in the single reserve used for withdrawal.
+The symbol `*` in front of a certain part means that it is repeated `n` times for the `n` coins.
+
+~~~
+ Wallet Exchange
+knows *denom.pub knows *denom.priv
+ | |
+reserve = EdDSA-Keygen() |
+persist (reserve, value) |
+ | |
+ |----------- (bank transfer) ----------->|
+ | (subject: reserve.pub, amount: value) |
+ | |
+ | persist (reserve.pub, value)
+*coin = EdDSA-Keygen() |
+*blind_secret = random(256) |
+persist *(coin, blind_secret) |
+*b = RSA-FDH-Blind(SHA-512(coin.pub), blind_secret, denom.pub)
+sig = EdDSA-Sign(reserve.priv, msg) |
+ | |
+ |--- /reserves/{reserve.pub}/withdraw -->|
+ | (*SHA-512(denom.pub), *b, sig) |
+ | |
+ | check *denom.pub valid
+ | check EdDSA-Verify(reserve.pub, msg, sig)
+ | check reserve.balance >= sum(*denom.valueAndFee)
+ | reserve.balance -= sum(*denom.valueAndFee)
+ | *blind_sig = RSA-FDH-Sign(b, denom.priv)
+ | persist withdrawal
+ | |
+ |<------------ *blind_sig ---------------|
+ | |
+*coin_sig = RSA-FDH-Unblind(blind_sig, blind_secret, denom.pub)
+check *RSA-FDH-Verify(SHA-512(coin.pub), coin_sig, denom.pub)
+persist *(coin, blind_secret, coin_sig)
+~~~
+
+where `msg` is formed as follows:
+
+~~~
+msg = bigEndian(32, 145) | bigEndian(32, 1200) /* TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW */
+ | bigEndianAmount(sum(*denom.value)) | bigEndianAmount(sum(*denom.fee_withdrawal))
+ | SHA-512( *SHA-512(SHA-512(denom.pub) | SHA-512(bigEndian(32, 0x1) | b | bytes(b))) )
+ | bigEndian(8, 0x00) | bigEndian(32, 0x00)
+~~~
+
+// todo: do we really want a uint8_t before a uint32_t?
+
+(for RSA, without age-restriction)
+
+### Implementation Details
+
+in wallet-core, `coin.priv` and `blind_secret` are derived from a random `secretSeed` as follows:
+
+~~~
+secretSeed = random(256)
+IKM = secretSeed
+info = "taler-withdrawal-coin-derivation"
+for coinIdx in 0..n-1:
+ salt = bigEndian(32, coinIdx)
+ tmp = HKDF(salt, IKM, info, 64)
+ coin.priv = tmp[:32]
+ blind_secret = tmp[32:]
+
+~~~
+
+
# Security Considerations
\[ TBD \]
diff --git a/draft-guetschow-taler-protocol.xml b/draft-guetschow-taler-protocol.xml
@@ -29,7 +29,7 @@
</address>
</author>
- <date year="2025" month="January" day="22"/>
+ <date year="2025" month="January" day="24"/>
<workgroup>independent</workgroup>
@@ -71,6 +71,7 @@ Use at your own risk!</t>
<t><spanx style="verb">bits(x)</spanx> denotes the minimal number of bits necessary to represent the multiple precision integer x</t>
<t><spanx style="verb">bytes(x)</spanx> denotes the minimal number of bytes necessary to represent the multiple precision integer x</t>
<t><spanx style="verb">bigEndian(16, x)</spanx> denotes the 16 least significant bits of the integer x encoded in network byte order (big-endian)</t>
+ <t><spanx style="verb">random(256)</spanx> denotes a randomly generated sequence of 256 bits</t>
<t><spanx style="verb">a * b (mod N)</spanx> denotes the multiplication, <spanx style="verb">a ** b (mod N)</spanx> the exponentiation of a and b, modulo N</t>
</list></t>
@@ -256,7 +257,7 @@ by verifying that the greatest common denominator (gcd) of <spanx style="verb">f
RSA-FDH-Derive(bks, pubkey) -> out
Inputs:
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -279,7 +280,7 @@ RSA-FDH-Blind(msg, bks, pubkey) -> out
Inputs:
msg message
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -323,7 +324,7 @@ RSA-FDH-Unblind(sig, bks, pubkey) -> out
Inputs:
sig blind signature
- bks blinding key secret of length L = 8 octets
+ bks blinding key secret of length L = 32 octets
pubkey RSA public key consisting of modulus N and public exponent e
Output:
@@ -372,6 +373,76 @@ out = (data == exp)
<section anchor="withdrawal"><name>Withdrawal</name>
+<t>The wallet creates <spanx style="verb">n > 0</spanx> coins and requests <spanx style="verb">n</spanx> signatures from the Exchange,
+giving values to the coins according to <spanx style="verb">n</spanx> denominations.
+The total value and withdrawal fee (defined by the Exchange per denomination)
+must be smaller or equal to the amount stored in the single reserve used for withdrawal.
+The symbol <spanx style="verb">*</spanx> in front of a certain part means that it is repeated <spanx style="verb">n</spanx> times for the <spanx style="verb">n</spanx> coins.</t>
+
+<figure><artwork><![CDATA[
+ Wallet Exchange
+knows *denom.pub knows *denom.priv
+ | |
+reserve = EdDSA-Keygen() |
+persist (reserve, value) |
+ | |
+ |----------- (bank transfer) ----------->|
+ | (subject: reserve.pub, amount: value) |
+ | |
+ | persist (reserve.pub, value)
+*coin = EdDSA-Keygen() |
+*blind_secret = random(256) |
+persist *(coin, blind_secret) |
+*b = RSA-FDH-Blind(SHA-512(coin.pub), blind_secret, denom.pub)
+sig = EdDSA-Sign(reserve.priv, msg) |
+ | |
+ |--- /reserves/{reserve.pub}/withdraw -->|
+ | (*SHA-512(denom.pub), *b, sig) |
+ | |
+ | check *denom.pub valid
+ | check EdDSA-Verify(reserve.pub, msg, sig)
+ | check reserve.balance >= sum(*denom.valueAndFee)
+ | reserve.balance -= sum(*denom.valueAndFee)
+ | *blind_sig = RSA-FDH-Sign(b, denom.priv)
+ | persist withdrawal
+ | |
+ |<------------ *blind_sig ---------------|
+ | |
+*coin_sig = RSA-FDH-Unblind(blind_sig, blind_secret, denom.pub)
+check *RSA-FDH-Verify(SHA-512(coin.pub), coin_sig, denom.pub)
+persist *(coin, blind_secret, coin_sig)
+]]></artwork></figure>
+
+<t>where <spanx style="verb">msg</spanx> is formed as follows:</t>
+
+<figure><artwork><![CDATA[
+msg = bigEndian(32, 145) | bigEndian(32, 1200) /* TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW */
+ | bigEndianAmount(sum(*denom.value)) | bigEndianAmount(sum(*denom.fee_withdrawal))
+ | SHA-512( *SHA-512(SHA-512(denom.pub) | SHA-512(bigEndian(32, 0x1) | b | bytes(b))) )
+ | bigEndian(8, 0x00) | bigEndian(32, 0x00)
+]]></artwork></figure>
+
+<t>// todo: do we really want a uint8_t before a uint32_t?</t>
+
+<t>(for RSA, without age-restriction)</t>
+
+<section anchor="implementation-details"><name>Implementation Details</name>
+
+<t>in wallet-core, <spanx style="verb">coin.priv</spanx> and <spanx style="verb">blind_secret</spanx> are derived from a random <spanx style="verb">secretSeed</spanx> as follows:</t>
+
+<figure><artwork><![CDATA[
+secretSeed = random(256)
+IKM = secretSeed
+info = "taler-withdrawal-coin-derivation"
+for coinIdx in 0..n-1:
+ salt = bigEndian(32, coinIdx)
+ tmp = HKDF(salt, IKM, info, 64)
+ coin.priv = tmp[:32]
+ blind_secret = tmp[32:]
+
+]]></artwork></figure>
+
+</section>
</section>
</section>
<section anchor="security-considerations"><name>Security Considerations</name>
@@ -480,7 +551,7 @@ out = (data == exp)
-<?line 352?>
+<?line 423?>
<section anchor="change-log"><name>Change log</name>
@@ -498,67 +569,88 @@ Education and Research (BMBF) within the project Concrete Contracts.</t>
</back>
<!-- ##markdown-source:
-H4sIAAAAAAAAA81ZUXfTyhF+16+YG15sajm2kxhwcU9DQkgOxHBIKA+56fVa
-WsvbyFqf1SqJoeGX9a1/rDOzK9tyDOEeaHs5nFhazc7Mznzz7awUhmFw3YOd
-ILDKprIHW+cTCa8GH+BcpNLAO6OtjnS6FcQ6ysQUJWIjxjZMCmnzaKJvQkuC
-4cwLBpGwMtFm3gOVjXUQqJnpgTVFbjut1rNWJ7jR5ioxupiRRCxnEv9kNsit
-kWJaHbuSc5SOewFACGyHryIzn1mdGDGbzHlARiKf8NVMzKc4Mw+CR3Ats0L2
-8ALAyJnuwcTaWd7b3k6UbSZZkUnb1CbZTvO4ha41cXibpVNcQm6X8iiwQX47
-CERhJ9qgeyEaB3DxOVVXOhUKXv37Xy5C/Awn9uD8wyEcGpnj4uBDpq6lyZWd
-gx7DuYwmmU51MmdpMRoZeU0TSnkephhJdOxYptOJTu0nHGhCu8UPI1TVq4hH
-OkZ/DsNWu9V95keKzFJuXkkzFZkzJqdCpT2YOr+bi8z+1RZh7NQ1YxkEmcY5
-Fr2mfLw/Oui0yot2a9df7j3tPvOX3c4Ojx6/PjxCL96eNNst/N96sv3sydNw
-J+zudsL2LkqFT37b2UXBs+OzhVy31Xm6PTg5O28enbw7a7aftsJdxBIiauFD
-EIRhiJHCGIjIBsGvF3D+4iP8eukeTFUcp5JwcIJL1nERWaWzitgLeSOMBDsR
-Fv+oHBDjBcEH8Dq3Kk2BwBqqjOCdYChyEFkMUzHHSGZWqAykMdrkzeBDLgHV
-zHVhQN9kYFR+9QtZH2grnOUQhltiFG0NASOqEWJoVEKqrDQipdyqLIEhSgxB
-ZpS7GEQO+2cHJydwwfG+JB0C/gmjqg50hsouY0MEJwE3yk5gRPIjZfPabb06
-Y6oyNUWrWTEdYZnjFBKDTEa4SGHmYDUVDS6ZwsEzitSqWSoBByOVkyGVYanj
-7Fs2M0fd32OH5H7IkEpeZrESWa3dbcC6wXYXUilyC7lKMjVWkUC1vDa0TQIL
-XYsgYxaxtCnT7ByWaozPa2gnlGyo7sL+GEZQm+oYBuuLdB6jLYp/g2UrwiQk
-b2c6wzWqlSQRmEYNQLEi1TAguBwsuU1FSL9qqgjuRGjrD4+R9OCoyBjYLPAI
-a2g/7Ox14fOjfCLw4i4Ivnz5Evjh2jRP6hD+BSbEl8FJNitsjzkAH9APxgKH
-YEq5SSQ5mcosQSS9gefQ+Xu3DTqykuj1bWEXk0kbK/HTYpUgf9LssbrFAHsd
-5O8bZL4+7HRKPeRcMCQFQyo6CpRmzTS7XAxWwQwzcibdSmG32W7AHv3p0h+K
-YrfZoRkXnncum8tw7LU7Lhx4sRIOvPvRcLQ7ez8nHt3d744HreZ+PDoUjw7F
-Y6eMx+434sFRreGmzLQRl8P1RZxCfvYHjdYaes6XMfKBi7RBPsFyi3OiFwri
-WBnUuZhZksH92MZyrDJHCp8/e9Dc9VwgrJzO0P5qPAL2vg/06KLV22lfOqeQ
-9Vc3Fo35cnUfq/EYt30YGz0tNW17kK/YvsDN8JIuHGUhByCJsq1rkRYS00l8
-cOpDto99CFGLIyA4QFrzfHB8un+AWZ1MRZlNGgkpmDXsrhro+K3lrGIoMC8z
-UuDycuzzElVIh30Ye9Jxu4wPYjVPFYCgJQZILiMjLd8u0SGsZ+xyJkmSW0tI
-xcIKZkyDTE6bhptaxRG6Af7X53TNI1db+JhLKxJpVKQM/7XQ+47m0gf5NXp7
-KI26dsFdI1zqbijAV/H4zmGRzKGmr0yDGs2oQ5E7a67NVtRY4G1uxeoOceE7
-qsuAA417lMcP7ZWVTCCgtGGwvLzlfohZ4OXtjH5yK2e0yLxIqLT8MsmPy14w
-pN/QzxqSXzkMGSSITgTnsAE3E4XbcSlIKtflEMBDqEXjJhYNY+2u3vRww0m1
-XKS2ASevTxt8KGjAG4bc29enHia5SyHJcQoZhtQU0QAjHmoCMp2FHkIGncAC
-4kf1P/Pk5T81RlGL/YO+VrjBo1HX0eE8pANRtloYYCTeT9Jo7N9oHjq4wmOI
-UpLCdhOzKNLAPRnrioPUBhJUKcxitugBIJ9h74LdByxaVp2teVnD1gRG2DWy
-C6EHq/OtzrJvvKR/hP56bK+5Run0jLhm4nkfOnt7j5H3+vC03W3VqyXz1i/4
-a3praPKNV12/x7Y0+14hjXWa6pvcM+a796/R8irCllioO/Y4dgRa8m9tdVes
-B2RjoYCQV0OVKyhaV8Eb2+pW4d0uSzU81TE2/oKqTzt25Qqu0D4X813DUfeC
-6xB2hcm4UMkrJndCFba3VMJ4n2EuE2zWsm/1sIOVwiBvaoMGrNXH5uIYlFn9
-uvJKEf2/q+g7yuh/UEe/D++VZA5WII/owOdlm4F6eA+JJU6cMnDKS5XTNpym
-cxiVIBPu2E007zZy9YlqxbK/Hg2lRB9aQawBb1TK7j6HgfP91tfBOpnSabBy
-IvKq6g131KoN6vXF4vsrsnwuHNTpCLV8N4Au/KkP7bJo8PSahS9ShVk5w/OU
-wAooO4uXMTJL+xlLbZZ4f7YfHh0e4+5ochGO48kdj2MXWsxm2lgK/cpuSib9
-FGqtGjArRpggLgacXC2GsuX0TSOPOXkgu3SNGOJOA6GVU1YcVvmsVeRYTQQ1
-L1aezUBW8YJm2cq4SNMQK4UO/LztkiL0QF9jvJzZ5sD3Fzhnvb9YIKGsedf9
-OaIJ0aO7u/vUybntw5YLyT4cnc/yj79sBVzM/bWcu0yXntTr90BREZAeEShV
-TlleyoBW3V/4utBa0hTnhmlqWR6YcyIlen/B6/clyU0OdeDU0ItEUIOD5YBF
-piKlMQs+Vag/GM0Bo6nGXJG+c5aQGMkv4zCL0ym1z3jsxioTFrudWhLFdUqF
-s0n5HJbOcgrazQqqQm7HZG10lVfAxZ3vKrhQgMMzIliTOwQjT5mrpxrcU1f3
-3Z+Pv7Kl3YQ/cnIT/jb0tz+EvxdlEHDuAn2VUXSft3ZMCR830Juth0DEOVgB
-EfNCqbSaNR51jPBg5jbRwh8um+VBlx3CxFDn7ovg4RSWpOozWDLrhgTyoakP
-myg1MMsHm6oiML9JlDD0GqukBfc2a0ExAa0GRVDwsTufrT1fppX2hXtZpcEa
-zUOjaL/MaK6SakZZdfmLVIKsQm/2qB8Ry6aqsm97fVgRnEd35/LIxzH5cCK9
-3CKTcTWT6AH4X7fjUWbYQ2Qxb9DnEkU2HDermSJ1fTefAu7dj31Ey9WshvRD
-NtpYK368hhq/o1rKZfCc5WL+mFVTZGW9LB391om+GuKHAa+ya5TBv9Lk/PLK
-bEY8Be0xOPmvQv5v5U5WTY8bdqXIKfq9XLYBeOMy5rwZ4KT/ch6sKWSDDgfk
-C723oOOD+q6s/GQSQ0/LfDxEUzWnp0+rWzkRHqQCW5TwjL69GUOv36mZca9k
-3Lv2xQdQ7nM/4mkzNuJG0C29fy0Mfb87oKjG/g1f7j8xHfInpkdwsj/YvyeB
-bTW9w6PvVCMRXfGLf6Qu3BRSndDdfnSV6ZtUxon7nPm5576gyLi/NRZpLrfu
-KnbO6ajKnzBu+EUPN9juPDsTxhIvUTvlPvvBkYz5m9Mpn1cMvY0LXsaFP3MR
-GN7LXAoTTaD24vTFkTtn+7eReCD8h4wsrYnYQNIF7/94/vsPBoP99U8eAAA=
+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
+pfx/AG6OOdPaKgAA
-->