18.80. DD 80: Short wire transfer subjects#
18.80.1. Summary#
Some mediums and clients do not support subjects big enough to fit an entire reserve public key. We need to add an endpoint to link a reserve public key to shorter sequences than can fit.
18.80.2. Problem#
Swiss users are used to QR-code based payments with a numeric payment identifier for invoices. Some Swiss banking apps do not support the (long-ish) public key wire transfer subject we use today. Furthermore, banks in NZ use an even shorter encoding with 3 very short fields to encode the reasons for a wire transfer. Other countries may also have restrictions that are, well, unfortunate. Finally, for Depolymerization we used a hack to encode the reserve public key, but also could be more efficient if we could use less entropy.
18.80.2.1. Handling small encoding space#
Using a very restricive alphabet like numeric and a very short wire subject length can leed us to quickly exaust most of the encoding space. We thus need a way to recycle old short subjects.
18.80.2.2. Parsing unstructured subjects#
We currently support two kinds of keys: reserve keys and KYC keys. We expect to need more key kind in the future, and using new crypto will make the encoded keys bigger.
We quickly found that users inputing those keys had a tendency to add undesirable separators and worst, some client even add them automatically. Those users where than very frustrated by the experience and somtimes where completly prevented from using Taler.
We also currently try to identify wire transfer subjects that are not well-formed and automatically bounce such wire transfers immediately. It would be great if we could make sure this happens more consistently (as right now small typos MAY still decode to a proper public key).
18.80.2.3. Repeated wire transfers#
Users may want to setup a periodic wire transfer into their wallet, which automatically tops-up their balance once a week or once a month. Here, we need to allow them to re-use the same wire transfer subject for a long time. However, the exchange logic requires a fresh reserve public key to be used each time.
18.80.3. Proposed Solution#
18.80.3.1. Wire transfer subject generation#
We need a new public endpoint where wallets can request a fresh wire transfer subject to be generated for them. The result MUST be some JSON as the different wire methods will require one or more fields with different constraints (see NZ vs. CH). Some may be numbers, others may be strings, there may be checksum-constraints (or not), we simply cannot enumerate all possibilities. So the new public API of the wire gateway (discoverable via the exchange) must have a plugin architecture and depending on the wire method generate a JSON object with fields specific to the wire method. If the underlying wire transfer subject format has no checksums, our wire transfer subject generator should add some (unless the entropy space is so tiny that even a small checksum is impractical).
We also need to associate a fresh wire transfer subject with a public key at that point, so the client should POST its registration public key to the backend.
The registration must have an expiration. How long can be configurable, the default should probably be at least a quarter. Usage of the registered wire transfer subject (by making such a wire transfer) should extend the expiration deadline. The current expiration should be returned by the service. Wallets must communicate the expiration in the user interface, making it clear that the registration will lapse (unless used) and then funds may end up with a different user.
The wire subject generation logic should avoid re-using wire transfer subjects, for example by (1) linearly going over the entropy space (modulo checksums and other constraints of the format), and (2) after cycling through it, skipping not only registered entries but also “recently expired” registrations (basically, keeping registrations around as blockers for a few days/weeks/months instead of immediately freeing the number).
Finally, we protect the service against exhaustion attacks where an attacker
drains our entropy space by sending too many registration requests. Here, an
adaptive proof-of-work is the only reasonable solution. Basically, as our
available entropy space goes down, we exponentially increase the
difficulty. The current difficulty could be exposed via /config. The
proof-of-work would be specific to the client’s public key being
registered. Unless the difficulty increased, the client is allowed to re-use
the proof-of-work in subsequent registration requests (if the original once
expired). If a client’s public key is already registered, we simply return
the same wire transfer subject again (idempotency!). However, once the
registration expired, the client is likely to get a different wire transfer
subject.
For wire transfer methods without actual restrictions on the wire transfer subject, we could simply return the registration public key in a suitable field of the response, in extreme cases skip storing data in our database, and might even accept a zero-cost proof-of-work.
18.80.3.2. Mapping to reserves#
Another endpoint allows wallets to map a registration public key to a unique reserve public key and a transaction type (like regular withdraw, KYC-auth, WAD, etc.). For this endpoint, the wallet would have to sign using the registration key. Each mapping is used at most once. If a client sends a new mapping before the old mapping has been used, the old mapping is simply discarded. Unused mappings expire when the registration expires.
18.80.3.3. Handling incoming wire transfers#
If the incoming wire transfer subject has a wire transfer gateway defined checksum, it should be checked and possibly bounced if the subject is not well-formed.
The wire transfer MUST also be bounced if the incomig wire transfer subject is not registered, or if the registration is expired, and if the wire transfer subject is not recognized as a registration public key (if the wire method has no entropy constraints and we skipped writing registrations to the database). For backwards-compatibility, reserve public keys (possibly with “KYC”-flag) should also be supported initially and not be bounced.
If a reserve public key and transaction type are already registered for the registration public key, the exchange is informed. Otherwise, the wire transfer is put in a “hold” state until either it passes some configurable hold delay or such a registration is received.
18.80.3.4. 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.
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.
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.
Client should not have any expectation about how subject derivation works and changing the method should be possible in a non breaking way.
SIMPLE: BASE32 encode and trunc
BTC: trunc and encode in segwit address
NZ-SBI: BASE32 encode and trunc 36 char
// Union discriminated by the "type" field.
type TransferSubject =
| SimpleSubject
| BitcoinSubject
| NzSbiSubject;
interface SimpleSubject {
// Subject for system accepting large subjects
type: "SIMPLE";
// Encoded string containing either the full key and transfer type or a derived short subject
subject: string;
}
interface BitcoinSubject {
// Subject for Bitcoin networks
type: "BTC";
// Encoded bitcoin address to add as recipent whith minimum amount.
address: string;
}
interface NzSbiSubject {
// Subject for New Zealand Settlement Before Interchange system
type: "NZ-SBI";
// 12 chars to set in the Particulars field
particulars: string;
// 12 chars to set in the Code field
code: string;
// 12 chars to set in the Reference field
reference: string
}
Sometimes some clients will have more restriction than others. We will sometimes supports many subject format.
// Subject format supported by the client
// SIMPLE: SEPA like format >= 140 chars
// CH-YUH: N chars ?
// BTC: bitcoin address
// NZ-SBI: New Zealand special three fields
type SubjectFormat =
| "SIMPLE"
| "CH-YUH"
| "BTC"
| "NZ-SBI";
The backend will expose the supported formats in its /config endpoint.
“SIMPLE” & “CH-YUH” -> SimpleSubject
“BTC” -> BitcoinSubject
“NZ-SBI” -> NzSbiSubject
18.80.3.5. Proof of Work#
To prevent bad actors from exhausting the encoding space we SHOULD add some PoW to each request. It’s stays optional are in some cases the encoding space is so large than no special encoding is used.
As the available entropy space goes down, the difficulty to register a new key increase automatically as new derived keys will collide with existing ones. The PoW difficulty is another layer of protection that should be based on request rate.
interface PowChallenge {
// Algorithm used to perform PoW, new ones will be added in the future
// PBKDF2-HMAC-SHA512: PBKDF2 using SHA-512
alg: "PBKDF2-HMAC-SHA512";
// How many iterations to run, must be > 0
iterations: Integer;
// Number of lead bits that must be zero for the challenge to be accepted,
// must be > 0
difficulty: Integer;
// Unique salt to use when solving the challenge
salt: string;
}
The backend provides a salt challenge encoded as a string and expects the nonce solution to be a string. This design will work with all clients on all platforms. The challenge can be linked to a request, a user, a session, etc. It can be reused for a certain number of requests or for a certain period of time. All of these options should be supported by the current API, allowing us to strengthen security in the future in a transparent manner.
18.80.3.6. Auditor#
Keep an history of all generated subject with their creation/expiration date for the auditor to match subject to keys.
18.80.3.7. API#
- GET /config#
Only new fields are listed there:
Response:
- 200 OK:
The exchange responds with a WireConfig object. This request should virtually always be successful.
Details:
interface WireConfig { // Supported formats for registration, there must at least one. supported_formats: SubjectFormat[]; }
- POST /registration#
Register a public key for wire transfer use.
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. 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 headers:
Taler-Challenge-PoW-SaltandTaler-Challenge-PoW-Nonce. 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.
Request:
interface SubjectRequest { // Public key algorithm; alg: "ECDSA"; // Encoded public key key: EddsaPublicKey; // Transfer types type: "reserve" | "kyc"; // Pow salt used pow: string; // Subject format requested format: SubjectFormat; // Optional expiration date, null or never will use a default expiration expiration?: Timestamp; // Optional mapping public key that will be used in the encoded subject instead map?: EddsaPublicKey; // Optional signature of the raw public key using the mapping key, // required if map is not null signature?: string; }
Response:
- 200 Ok:
Response is a SubjectResult.
- 202 Accepted:
PoW is required for this operation. This returns the PowChallenge response.
- 400 Bad request:
Input data was invalid.
- 409 Conflict:
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:
interface SubjectResult { // Subject to use subject: TransferSubject; // Expiration date after which this subject can be reused and if mapping // is used when it expired. expiration: Timestamp; }
18.80.4. Test Plan#
18.80.5. Definition of Done#
New endpoints supported by all wire gateways
Support for Swiss-specific wire transfer subjects
Exchange points wallets/merchants to wire gateway APIs
Wallets support registration
Wallets have UI where the user can specify “periodic” wire transfers where the wallets periodically try to map new reserve public keys to an existing registration
Optional: remove legacy mode?
18.80.6. Alternatives#
The mapping of registration key to reserve key could also specify the amount. That may help map multiple wire transfers to the “right” key, but would create problems for the UX if the user got the amount wrong.
18.80.7. Drawbacks#
Theoretically, transfering money using a long-expired wire transfer subject may send money to a different wallet. This seems to be unavoidable with low-entropy wire transfer subjects.