commit 1abafe19ee9fbfd194fb58b77a4ed154bf3ec8cd
parent 61094f3cb7110248757d1aa271ff9170439bb218
Author: Mikolai Gütschow <mikolai.guetschow@tu-dresden.de>
Date: Mon, 27 Jan 2025 13:09:12 +0100
-make coin derivation from master_secret explicit
Diffstat:
2 files changed, 166 insertions(+), 130 deletions(-)
diff --git a/draft-guetschow-taler-protocol.md b/draft-guetschow-taler-protocol.md
@@ -346,14 +346,16 @@ out = (data == exp)
# The Taler Crypto Protocol
// todo: explain persist, check
+// todo: add KYC check
## Withdrawal
-The wallet creates `n > 0` coins and requests `n` signatures from the Exchange,
-giving values to the coins according to `n` denominations.
+The wallet creates `n > 0` coins and requests `n` signatures from the exchange,
+attributing value to the coins according to `n` chosen 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.
+The symbol `*` in front of a certain part means that it is repeated `n` times for the `n` coins,
+where `*?` denotes the index number `0 <= *? <= n`.
~~~
Wallet Exchange
@@ -365,15 +367,15 @@ 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)
+ | persist (reserve.pub, value)
+master_secret = random(256) |
+persist master_secret |
+*(coin, blind_secret) = GenerateCoin(master_secret, *?) |
+*blind_coin = 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) |
+ |(*SHA-512(denom.pub), *blind_coin, sig) |
| |
| check *denom.pub known and not withdrawal-expired
| check EdDSA-Verify(reserve.pub, msg, sig)
@@ -400,26 +402,34 @@ msg = bigEndian(32, 40) | bigEndian(32, 1200) /* TALER_SIGNATURE_WALLET_RESERVE_
| SHA-512(commit)
~~~
-(for RSA, without age-restriction)
+The wallet derives coins and blinding secrets using `GenerateCoin` from a master secret and an integer index.
+This is strictly speaking an implementation detail since the master secret is never revealed to any other party,
+and might be chosen to be implemented differently.
-// todo: add KYC check
+// todo: discuss with Florian: use reserve.priv as master_secret?
-### Implementation Details
+~~~
+GenerateCoin(secret, idx) -> (coin, bks)
-in wallet-core, `coin.priv` and `blind_secret` are derived from a random `secretSeed` as follows:
+Inputs:
+ secret secret to derive coin from
+ idx coin index
+Output:
+ coin private-public keypair of the coin
+ bks random blinding key secret of length 32 bytes
~~~
-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:]
+`coin` and `blind_secret` are calculated as follows:
+
+~~~
+tmp = HKDF(bigEndian(32, *?), master_secret, "taler-withdrawal-coin-derivation", 64)
+(coin.priv, bks) = (tmp[:32], tmp[32:])
+coin.pub = EdDSA-GetPub(coin.priv)
~~~
+(for RSA, without age-restriction)
+
# Security Considerations
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="25"/>
+ <date year="2025" month="January" day="27"/>
<workgroup>independent</workgroup>
@@ -68,9 +68,14 @@ Use at your own risk!</t>
<t><list style="symbols">
<t><spanx style="verb">"abc"</spanx> denotes the literal string <spanx style="verb">abc</spanx> encoded as ASCII <xref target="RFC20"></xref></t>
<t><spanx style="verb">a | b</spanx> denotes the concatenation of a with b</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">padZero(12, a)</spanx> denotes the byte string a, zero-padded to the length of 12 bytes</t>
+ <t><spanx style="verb">bits(x)</spanx> (<spanx style="verb">bytes(x)</spanx>) denotes the minimal number of bits (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">bigEndianAmount(amount)</spanx> is formed from a fixed-point representation of <spanx style="verb">amount</spanx>
+as <spanx style="verb">bigEndian(64, amount.value) | bigEndian(32, amount.fraction) | padZero(12, amount.currency)</spanx>,
+where <spanx style="verb">amount.value</spanx> is the non-negative integer part of the base currency,
+<spanx style="verb">amount.fraction</spanx> is given in unites of one hundred millionth (1e-8) of the base currency,
+and <spanx style="verb">amount.currency</spanx> are the 3-11 ASCII characters used as currency code by the exchange.</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>
@@ -371,15 +376,17 @@ out = (data == exp)
</section>
<section anchor="the-taler-crypto-protocol"><name>The Taler Crypto Protocol</name>
-<t>// todo: explain persist, check</t>
+<t>// todo: explain persist, check
+// todo: add KYC check</t>
<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.
+<t>The wallet creates <spanx style="verb">n > 0</spanx> coins and requests <spanx style="verb">n</spanx> signatures from the exchange,
+attributing value to the coins according to <spanx style="verb">n</spanx> chosen 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>
+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,
+where <spanx style="verb">*?</spanx> denotes the index number <spanx style="verb">0 <= *? <= n</spanx>.</t>
<figure><artwork><![CDATA[
Wallet Exchange
@@ -391,17 +398,17 @@ 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)
+ | persist (reserve.pub, value)
+master_secret = random(256) |
+persist master_secret |
+*(coin, blind_secret) = GenerateCoin(master_secret, *?) |
+*blind_coin = 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) |
+ |(*SHA-512(denom.pub), *blind_coin, sig) |
| |
- | check *denom.pub valid
+ | check *denom.pub known and not withdrawal-expired
| check EdDSA-Verify(reserve.pub, msg, sig)
| check reserve.balance >= sum(*denom.valueAndFee)
| reserve.balance -= sum(*denom.valueAndFee)
@@ -426,25 +433,34 @@ msg = bigEndian(32, 40) | bigEndian(32, 1200) /* TALER_SIGNATURE_WALLET_RESERVE_
| SHA-512(commit)
]]></artwork></figure>
-<t>(for RSA, without age-restriction)</t>
-
-<section anchor="implementation-details"><name>Implementation Details</name>
+<t>The wallet derives coins and blinding secrets using <spanx style="verb">GenerateCoin</spanx> from a master secret and an integer index.
+This is strictly speaking an implementation detail since the master secret is never revealed to any other party,
+and might be chosen to be implemented differently.</t>
-<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>
+<t>// todo: discuss with Florian: use reserve.priv as master_secret?</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:]
+GenerateCoin(secret, idx) -> (coin, bks)
+
+Inputs:
+ secret secret to derive coin from
+ idx coin index
+Output:
+ coin private-public keypair of the coin
+ bks random blinding key secret of length 32 bytes
]]></artwork></figure>
-</section>
+<t><spanx style="verb">coin</spanx> and <spanx style="verb">blind_secret</spanx> are calculated as follows:</t>
+
+<figure><artwork><![CDATA[
+tmp = HKDF(bigEndian(32, *?), master_secret, "taler-withdrawal-coin-derivation", 64)
+(coin.priv, bks) = (tmp[:32], tmp[32:])
+coin.pub = EdDSA-GetPub(coin.priv)
+]]></artwork></figure>
+
+<t>(for RSA, without age-restriction)</t>
+
</section>
</section>
<section anchor="security-considerations"><name>Security Considerations</name>
@@ -553,7 +569,7 @@ for coinIdx in 0..n-1:
-<?line 425?>
+<?line 442?>
<section anchor="change-log"><name>Change log</name>
@@ -571,87 +587,97 @@ Education and Research (BMBF) within the project Concrete Contracts.</t>
</back>
<!-- ##markdown-source:
-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
+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
-->