taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 5dffbf846dac3499f7bccb36fcb62e0eb1bf40b5
parent 63fe26cb1845878094469c437302d57ab48d8527
Author: Antoine A <>
Date:   Tue, 17 Feb 2026 14:36:16 +0100

dd80: improve subject derivation

Diffstat:
Mdesign-documents/080-short-wire-subject.rst | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 61 insertions(+), 19 deletions(-)

diff --git a/design-documents/080-short-wire-subject.rst b/design-documents/080-short-wire-subject.rst @@ -140,17 +140,59 @@ configurable **hold delay** or such a registration is received. Subject derivation ------------------ -When the encoding space is not limited or large enough like in SEPA transfers, no special derivation logic is used and we use the existing one. +When the encoding space is not limited or large enough like in SEPA transfers, +no special derivation logic is used and we use the existing one. -The manner in which subjects are derived is not specified but the API must be idempotent and the derivation method must use the entire coding space. +The manner in which subjects are derived is voluntarily not specified, clients +should not expect anything about the derivation logic and treat it as a black box. -An efficient way is to make the derivation idempotent and use hashing, for example you can make a SHA-512 hash of the key and metadata: SHA-512("RESERVE" + "ECDSA" + raw key bytes) or SHA-512("KYC" + "SLH-DSA" + raw key bytes). Then truncate to the number of bytes we can fill in the subject and encoded using a supported alphabet. +As the API must idempotent and derivation must use the entire coding space I expect +all implementation to converge on the same deterministic algorithm. -Client should not have any expectation about how subject derivation works and changing the method should be possible in a non breaking way. +All non trivial subject derivation would start by making a SHA-512 hash of the key and metadata: +SHA-512("RESERVE" + "ECDSA" + raw key bytes) or SHA-512("KYC" + "SLH-DSA" + raw key bytes). -- SIMPLE: BASE32 encode and trunc +For binary subject format like BTC we simply truncate the hash. For text subject format it's way +harder as we need to choose the alphabet to use for encoding. +We currently use Crockford's Base32 because it is case-insensitive and typo complacent. +However it make a poor use of the available entropy. + +We will have some format that only accept numbers 0-9 (10 chars), some will only accept simple +ASCII A-Za-z0-9 (62 chars) and most will also accept some special characters A-Za-z0-9-: (64 characters). + +When we have enough space we should simply use our current Crockford’s Base32 and truncate the subject. +It's working well and is efficient. With 16 characters we can encode 80bits and that is enough entropy +for our use case. + +If we have access to less characters or have very limited alpahbet we will have to use a more complex +approach an Arbitrary-Precision Base Conversion algorithm. We treat the hash as a single unsigned integer in base 256 +that we convert into a base A (length of the alphabet). Then using successive euclidean division we can ensure +mathematical fariness across any alphabet size. + +.. code-block:: kotlin + + import java.math.BigInteger + import java.security.MessageDigest + + fun deterministicEncode(data: ByteArray, alphabet: String, n: Int): String { + val hash = MessageDigest.getInstance("SHA-512").digest(data) + + var value = BigInteger(1, hash) + val base = BigInteger.valueOf(alphabet.length.toLong()) + val result = StringBuilder() + + repeat(n) { + val (quotient, remainder) = value.divideAndRemainder(base) + result.append(alphabet[remainder.toInt()]) + value = quotient + } + return result.reverse().toString() + } + +- SIMPLE: no special encoding use the current very verbose encoding +- CH-YUH: BASE32 encode and trunc N chars +- NZ-SBI: BASE32 encode and trunc 36 char split in 3 chunk of 12 char - BTC: trunc and encode in segwit address -- NZ-SBI: BASE32 encode and trunc 36 char .. ts:def:: TransferSubject @@ -202,14 +244,14 @@ Sometimes some clients will have more restriction than others. We will sometimes // Subject format supported by the client // SIMPLE: SEPA like format >= 140 chars - // CH-YUH: N chars ? - // BTC: bitcoin address + // CH-YUH: N chars ? // NZ-SBI: New Zealand special three fields + // BTC: bitcoin address type SubjectFormat = | "SIMPLE" | "CH-YUH" - | "BTC" - | "NZ-SBI"; + | "NZ-SBI" + | "BTC"; The backend will expose the supported formats in its ``/config`` endpoint. @@ -234,7 +276,7 @@ As the available entropy space goes down, the difficulty to register a new key i // How many iterations to run, must be > 0 iterations: Integer; - // Number of lead bits that must be zero for the challenge to be accepted, + // Number of lead bits that must be zero for the challenge to be accepted, // must be > 0 difficulty: Integer; @@ -277,17 +319,17 @@ API .. http:post:: /registration Register a public key for wire transfer use. - - This endpoint generate an appropriate subject to link a transfer to the + + This endpoint generate an appropriate subject to link a transfer to the registered public key. - A mapping public key can also be used for repeated wire transfers. + A mapping public key can also be used for repeated wire transfers. Reusing a mapping public key replace previous mapping. - As this endpoint is unauthenticated a PoW challenge may need to be - solved. In this case the solution is included using the following HTTP + As this endpoint is unauthenticated a PoW challenge may need to be + solved. In this case the solution is included using the following HTTP headers: ``Taler-Challenge-PoW-Salt`` and ``Taler-Challenge-PoW-Nonce``. - A challenge solution might be reused, clients should store and reuse + A challenge solution might be reused, clients should store and reuse previous solution. A successfully completed challenge may be followed by a new challenge, and several challenges may need to be successfully completed. The client must handle this correctly and try again with the new challenge. @@ -295,7 +337,7 @@ API **Request:** .. ts:def:: SubjectRequest - + interface SubjectRequest { // Public key algorithm; alg: "ECDSA"; @@ -332,7 +374,7 @@ API * ``TALER_EC_BANK_UNSUPPORTED_FORMAT``: format is not supported. * ``TALER_EC_BANK_DERIVATION_REUSE``: derived short subject is already used, you should retry using another key. * ``TALER_EC_BANK_BAD_SIGNATURE``: signature is invalid. - + **Details:**