18.80. DD 80: Alternative wire transfer subjects#

18.80.1. Summary#

Some mediums and clients do not support subjects large enough to contain an entire reserve public key, and filling in the subject manually is very error-prone. We need a way to generate new input methods for wire transfers that are linked to the metadata currenlty stored in the unstructured subject. We also need support for recurring transfers.

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#

The subject derivation must be deterministic and and use the entire coding space.

We will continue to support the current encoding for simple cases.

All subject derivation starts by making a hash of the key and metadata as follow:

  • SHA-512(“RESERVE:ECDSA” + raw key bytes)

  • SHA-512(“KYC:SLH-DSA” + raw key bytes)

TODO do we really nead SHA-512. Why not SHA-256 as we will never be able to fit all this entropy.

Then the hash bits are encoded differently for each subject format:

18.80.3.4.1. Swiss QR-bill#

Treat the whole hash as a big integer then modulo by 10 power 26. Encode the remainder into a string and add the 27th checksum character according to QR-bill spec.

18.80.3.5. Auditor#

By running the subject derivation logic itself, the auditor can match corresponding transfers.

18.80.3.6. Taler Wire Transfer HTTP API#

GET /config#

Response:

200 OK:

The exchange responds with a WireConfig object. This request should virtually always be successful.

Details:

type SubjectFormat =
  | "SIMPLE"
  | "URI"
  | "CH_QR_BILL";
interface WireTransferConfig {
  // 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 must be used to allow recurrent wire transfers. Reusing a mapping public key replace previous mapping.

Request:

interface SubjectRequest {
  // Subject format requested
  format: SubjectFormat;

  // Amount to transfer
  credit_amount: Amount;

  // Transfer types
  type: "reserve" | "kyc";

  // Public key algorithm
  alg: "ECDSA";

  // Account public key
  account_pub: EddsaPublicKey;

  // Public key encoded inside the subject
  authorization_key: EddsaPublicKey;

  // Signature of the account_pub public key using the authorization_key private key
  authorization_signature: EddsaSignature;

  // Whether the authorization_key will be reused for recurrent transfers
  // Disable bounces in case of authorization_key reuse
  recurrent: boolean;
}

Response:

200 Ok:

Response is a SubjectResult.

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:

// Union discriminated by the "type" field.
type TransferSubject =
| SimpleSubject
| UriSubject
| SwissQrBillSubject;
interface SimpleSubject {
  // Subject for system accepting large subjects
  type: "SIMPLE";

  // Amount to transfer
  credit_amount: Amount;

  // Encoded string containing either the full key and transfer type or a derived short subject
  subject: string;
}
interface UriSubject {
  // Subject for system accepting prepared payments
  type: "URI";

  // Prepared payments confirmation URI
  uri: string;
}
interface SwissQrBillSubject {
  // Subject for Swiss QR Bill
  type: "CH_QR_BILL";

  // Amount to transfer
  credit_amount: Amount;

  // 27-digit QR Reference number
  reference: string;
}
interface SubjectResult {
  // Subject to use
  subject: TransferSubject;

  // Expiration date after which this subject will be reused
  expiration: Timestamp;
}
DELETE /registration#

Remove an existing registration.

Use this endpoint to free a derived subject or cancel a recurrent paiment.

Request:

interface Unregistration {
    // Current timestamp in the ISO 8601
    timestamp: string;

    // Public key used for registration
    authorization_key: EddsaPublicKey;

    // Signature of the timestamp using the authorization_key private key
    // Necessary to prevent replay attack
    authorization_signature: EddsaSignature;
}

Response:

204 No content:

The registration have been deleted.

400 Bad request:

Input data was invalid.

404 Not found:

Unknown registration.

409 Conflict:
  • TALER_EC_BANK_OLD_TIMESTAMP: the timestamp is too old.

  • TALER_EC_BANK_BAD_SIGNATURE: signature is invalid.

18.80.4. Test Plan#

18.80.5. Definition of Done#

  • New API supported by all wire gateways

  • Support for Swiss QR Bill wire transfer subjects

  • Exchange points wallets/merchants to wire transfer API

  • 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.

18.80.8. Discussion / Q&A#