commit 06665cc8f0f638ac77290e4a8d91b5d969c4bd3b
parent 352c11ebc347ef99fee8786d1f24c8e856764e93
Author: Özgür Kesim <oec-taler@kesim.org>
Date: Mon, 21 Apr 2025 22:41:13 +0200
[dd:pg-refresh] make refresh-protocol more bandwidth efficient in reveal
Diffstat:
2 files changed, 46 insertions(+), 21 deletions(-)
diff --git a/core/api-exchange.rst b/core/api-exchange.rst
@@ -2153,9 +2153,11 @@ These endpoints are called by the client
// depths first: [0..n)[0..n)[0..n)
rc: string;
- // The disclosed kappa-1 arrays of signatures, one per coin, signed by
- // the old coin's private key, over the derived nonce values per coin.
- signatures: CoinSignature[kappa-1][];
+ // The disclosed kappa-1 signatures by the old coin's private key,
+ // over Hash1a("Refresh", Cp, r, i), where Cp is the melted coin's public key,
+ // r is the public refresh nonce from the metling step and i runs over the
+ // _disclosed_ kappa-1 indices.
+ signatures: CoinSignature[kappa-1];
// IFF the denomination of the old coin had support for age restriction,
// the client MUST provide the original age commitment, i. e. the
diff --git a/design-documents/062-pq-refresh.rst b/design-documents/062-pq-refresh.rst
@@ -44,8 +44,8 @@ derive the key material of a fresh coin from the old coin:
.. sourcecode:: python
# Notation:
- # r = random seed, cs = coin secret, Cp = coin public key
- # pkD = denomination key
+ # r = random seed, cs = dirty coin secret, Cp = dirty coin public key
+ # pkD = denomination public key
def RefreshDerive(r, cs, Cp, pkD):
t = Hash1a("Refresh", Cp, r)
@@ -65,6 +65,25 @@ The hash functions ``Hash1x`` might be the same, but can be pair-wise
different. However, the hash function ``Hash2`` must be different from
all of the other.
+A variant of this protocol that is suitable for retrieving a batch of n coins
+from an dirty coin is as follows:
+
+.. sourcecode:: python
+
+ # Notation:
+ # r = random dees, cs = dirty coin's secret, Cp = dirty coin's public key
+ # pkD[] = array of denomination public keys
+ def RefreshDeriveBatch(r, cs, Cp, pkDs: list[denomPublicKey]):
+ t = Hash1a("Refresh", Cp, r, pkDs)
+ s = SignDeterministic(cs, t)
+ for i, pkD in enumerate(pkDs):
+ x[i] = Hash1b(s, i)
+ b[i] = Hash2(s, i)
+ c2_s[i], C2_p[i] = KeyGen(x[i])
+ m[i] = Blind(C2_p[i], b[i], pkD)
+ return (s, c2_s, C2_p, m)
+
+
Protocol Modifications
^^^^^^^^^^^^^^^^^^^^^^
@@ -75,32 +94,34 @@ published.
1. **Melting/Commit Phase**:
- Client chooses a master seed r and derives κ nonces r_1, ... r_κ.
- - Client generates κ refresh blinded coin candidates m_1,... m_κ from them.
- - Sends dirty coin, r, all m_i and new denom-info to the exchange, with
- signature σ_c of the dirty coins' private key over the request.
+ - Client generates, using RefreshDeriveBatch, κ*n blinded coin planchets
+ m[1][1],...,m[1][n],...,m[κ][1],..,m[κ][n] from the seeds.
+ - Sends dirty coin, r, all m[i][j] and new denom-info pkD[] to the exchange,
+ with signature σ_c of the dirty coins' private key over the request.
- Exchange verifies the request.
- - Exchange calculates h_m = H(m_1,...m_κ)
- - Exchange chooses γ from 1...K and signs m_γ, resulting in σ_γ.
- - Exchange persists h_m → (r, γ, m_γ, σ_γ, σ_c) and returns γ to the client.
+ - Exchange calculates h_m = H(pkD[], m[][], <more meta data>)
+ - Exchange chooses γ from 1...κ and signs all m[γ][], resulting in σ[γ][].
+ - Exchange persists h_m → (r, γ, pkD[], m[γ][], σ[γ][], σ_c) and
+ returns γ to the client.
2. **Reveal Phase**:
- Client discloses together with h_m all except the γ-th
- signatures s_i from the call to RefreshDerive.
- - Exchange verifies signature s_i over Hash1a("Refresh", C_p, r_i).
- - Exchange reconstructs the blinded coins m'_i.
- - Exchange verifies h_m = H(m'_1, ..., m_γ, ... m'_κ) equality.
- - Exchange returns σ_γ on success.
+ signatures s[1],...,s[κ] from the κ calls to RefreshDeriveBatch.
+ - Exchange verifies each signature s[i] over Hash1a("Refresh", C_p, r_i, pkDs).
+ - Exchange reconstructs the blinded coins m'[][] (except the γ-th).
+ - Exchange verifies h_m = H(pkD[], m'[1][],...,m[γ][],...,m'[κ][], ...) equality.
+ - Exchange returns σ[γ][] on success.
It is worth noting that, in contrast to the existing refresh protocol, the
-client sends all κ tuples of m_i already in the commitment phase. This is
+client sends all n*κ tuples m[][] already in the commitment phase. This is
necessary such that the exchange can sign the request with a valid denomination
key *at the moment of melting*. This ensures idempotency of the melting/commit
request and that caries over to the reveal phase.
Note that for the Linking protocol, given the dirty coin's public key,
the Exchange simply returns the master seed r and the dirty coins' signature
-σ_c over the original refresh request. The owner of the private key of the
+σ_c over the original refresh request. The owner of the private key of the
dirty coin can then replay the refresh protocol and can be sure that the master
seed was of its own origin.
@@ -247,9 +268,11 @@ TODO: explain /reveal-melt endpoint.
// depths first: [0..n)[0..n)[0..n)
rc: HashCode;
- // The disclosed kappa-1 arrays of signatures, one per coin, signed by
- // the old coin's private key, over the derived nonce values per coin.
- signatures: CoinSignature[kappa-1][];
+ // The disclosed kappa-1 signatures by the old coin's private key,
+ // over Hash1a("Refresh", Cp, r, i), where Cp is the melted coin's public key,
+ // r is the public refresh nonce from the metling step and i runs over the
+ // _disclosed_ kappa-1 indices.
+ signatures: CoinSignature[kappa-1];
// IFF the denomination of the old coin had support for age restriction,
// the client MUST provide the original age commitment, i. e. the