20.98. DD 98: Token Fountains for Promotions#

20.98.1. Summary#

This document proposes token fountains as a simple way for institutions to distribute promotional discount and subscription tokens. A wallet imports a bearer credential, periodically retrieves the available grants and withdraws blind-signed tokens within limits configured by the institution.

Note

This design document proposes an extension for discussion. The protocol version name vTokenFountains is a placeholder.

20.98.2. Motivation#

Institutions sometimes want to distribute promotional benefits to a group without creating and paying a separate order for every recipient. For example, a university may give students discount tokens accepted by a campus merchant, or periodically provide subscription tokens for a continuing promotion.

GNU Taler already defines blind-signed merchant tokens. What is missing is a simple distribution channel through which a wallet can obtain an authorized set of those tokens over time. A token fountain is a bearer credential that authorizes such withdrawals.

The institution can distribute a fountain as a QR code or deep link. The wallet imports the link, learns which token families are available and withdraws blind-signed tokens subject to the configured limits. Blind signatures keep a later use of a token unlinkable from its withdrawal.

20.98.3. Requirements#

  • Institutions must be able to distribute promotional tokens without creating a separate order for every withdrawal.

  • A fountain must support grants for existing discount and subscription token families.

  • Token withdrawals must use blind signatures so that later token use is not linkable to withdrawal.

  • The merchant backend must enforce per-period withdrawal limits atomically.

  • Wallets must be able to import a fountain using a QR code or Taler URI.

  • The bearer secret must not appear in HTTP request URLs after onboarding.

  • Deployments must be able to update or revoke future withdrawals without invalidating tokens that were already issued.

20.98.4. Proposed Solution#

20.98.4.1. Terminology#

In protocol descriptions, this document uses the existing token-family kind values:

  • A discount token is useful for a one-time or otherwise limited promotion. It may be consumed without the merchant issuing a replacement.

  • A subscription token represents a continuing promotion. When an order consumes it, the existing merchant token mechanism can return a replacement token envelope as an output of the selected choice.

User-facing applications may call either benefit a pass. The protocol continues to distinguish them by token-family kind.

20.98.4.2. Fountain Credential#

A fountain has:

  • a public fountain_id identifying the fountain within a merchant instance;

  • a random fountain_secret used as its bearer credential;

  • a polling frequency; and

  • one or more grants describing which token families may be withdrawn and at what rate.

The onboarding URI is:

taler://fountain/$MERCHANT_HOST[/$INSTANCE_PATH]/$FOUNTAIN_ID/$FOUNTAIN_SECRET

The URI may be displayed as a QR code or opened as a wallet deep link. fountain_id tells the wallet which fountain it is importing, while fountain_secret authenticates the import. The wallet must treat the full URI as sensitive.

After import, the secret must not be placed in subsequent HTTP URLs. It is sent in the Authorization header when reading fountain information and in the request body when withdrawing tokens, as specified by the merchant API.

20.98.4.3. Deployment Models#

An institution may use one fountain for an entire campaign. This is simple to distribute, but every recipient shares one bearer credential and the same withdrawal limits. A leaked link cannot be revoked for only one recipient.

Alternatively, the institution may create one fountain per recipient and distribute each link through its existing student communication system. This allows independent limits and revocation without requiring the merchant backend to store the student’s real identity. The fountain description can contain an opaque reference meaningful only to the institution.

The choice is operational rather than cryptographic. Blind token issuance prevents the merchant from linking a withdrawn token to its later use in either model.

20.98.4.4. Grant Model#

Each FountainGrant names a token family and defines:

  • tokens_per_period_limit, the maximum number of tokens signed for one issue-key validity period;

  • tokens_per_period_stash, the number of tokens the wallet should try to keep available for that period; and

  • key_window_size, the number of issue-key slots into the future for which the wallet may prepare tokens.

The merchant may update grants without replacing the bearer credential. Wallets periodically retrieve the current grants according to poll_freq. Removing a grant prevents future withdrawals from that family, but tokens already withdrawn remain valid according to the token family’s issue keys.

20.98.4.5. Protocol Flow#

  1. The institution configures suitable discount or subscription token families on the merchant backend.

  2. It creates a fountain with grants for those token families.

  3. The backend returns fountain_id and fountain_secret once. The institution forms the Taler fountain URI and gives it to the intended recipients.

  4. A wallet imports the URI and calls GET /fountain/info using the bearer credential.

  5. The wallet prepares blinded token envelopes for any grants it needs to replenish and submits them to POST /fountain/withdraw.

  6. The backend checks the grant, issue-key window and withdrawal limit, then blind-signs the accepted envelopes.

  7. The wallet unblinds and stores the tokens. Their later use follows the existing merchant token protocol.

20.98.4.6. Merchant API#

20.98.4.6.1. Private Management Operations#

The merchant API adds private operations to create, inspect, update, list and delete fountains:

  • POST /private/fountains

  • GET /private/fountains

  • GET /private/fountains/$FOUNTAIN_ID

  • PATCH /private/fountains/$FOUNTAIN_ID

  • DELETE /private/fountains/$FOUNTAIN_ID

Creation returns:

interface FountainCreateResponse {
  // Public identifier included in the wallet onboarding URI.
  fountain_id: string;

  // 256-bit bearer credential, Crockford Base32 encoded.
  // Returned only at creation; the backend stores only its hash.
  fountain_secret: string;
}

20.98.4.6.2. Wallet Operations#

GET /fountain/info returns the current grants, token-family metadata and issue public keys needed to prepare blinded envelopes.

POST /fountain/withdraw accepts the bearer credential and blinded envelopes. The request is grouped by token family and issue-key slot so that the backend can enforce each grant’s limits.

Deletion invalidates future requests using the credential. It does not invalidate tokens that the wallet already withdrew.

20.98.5. Security Considerations#

Anyone possessing a fountain URI can withdraw under its grants. Distribution systems must therefore protect the URI like any bearer credential. Wallets must avoid exposing it in history, telemetry, screenshots or backups that are not intended to contain secrets.

The backend stores only a hash of fountain_secret. It must rate-limit failed authentication and enforce withdrawal limits atomically to prevent concurrent requests from exceeding a grant.

Fountains are not proof of student identity. Eligibility is decided by the institution when it distributes the link. A shared campaign link can be forwarded, while a per-recipient link can only be individually disabled after the institution learns that it was compromised.

20.98.6. Privacy Considerations#

Fountain descriptions should contain opaque administrative references rather than names or student identifiers. The merchant backend does not need a recipient’s identity to issue blind-signed tokens.

Network metadata can still correlate a wallet’s requests. Deployments that require stronger network-level privacy need an appropriate transport or proxy; blind signatures alone do not hide IP addresses or request timing.

20.98.7. Drawbacks#

The fountain API adds persistent bearer credentials, polling and quota state to the merchant backend.

Shared campaign fountains trade administrative simplicity for coarse revocation and shared quotas. Per-recipient fountains provide better control but require the institution to create and distribute many distinct links.

20.98.8. Test Plan#

  • Test creation, inspection, update, listing and deletion of fountains.

  • Test importing a URI containing both fountain_id and fountain_secret.

  • Test withdrawals for both discount and subscription token families.

  • Test atomic enforcement of per-period limits under concurrent requests.

  • Test rejection of unknown grants, unsupported issue-key slots and invalid credentials.

  • Test that deleting a fountain prevents new withdrawals without invalidating tokens already held by a wallet.

  • Test that token use cannot be linked to its fountain withdrawal from the blind-signature transcript.

20.98.9. Definition of Done#

  • The merchant backend implements fountain management, information and withdrawal operations.

  • The onboarding URI includes the merchant location, fountain_id and fountain_secret.

  • Wallet Core can import a fountain, poll its grants, replenish tokens within the configured limits and stop when the credential is revoked.

  • Merchant applications can create and distribute promotional fountains without supplying recipient identities to the merchant backend.

  • Discount and subscription token families retain their existing consumption and replacement semantics.