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
discountandsubscriptiontoken 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
discounttoken is useful for a one-time or otherwise limited promotion. It may be consumed without the merchant issuing a replacement.A
subscriptiontoken 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_ididentifying the fountain within a merchant instance;a random
fountain_secretused 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; andkey_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#
The institution configures suitable
discountorsubscriptiontoken families on the merchant backend.It creates a fountain with grants for those token families.
The backend returns
fountain_idandfountain_secretonce. The institution forms the Taler fountain URI and gives it to the intended recipients.A wallet imports the URI and calls
GET /fountain/infousing the bearer credential.The wallet prepares blinded token envelopes for any grants it needs to replenish and submits them to
POST /fountain/withdraw.The backend checks the grant, issue-key window and withdrawal limit, then blind-signs the accepted envelopes.
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/fountainsGET /private/fountainsGET /private/fountains/$FOUNTAIN_IDPATCH /private/fountains/$FOUNTAIN_IDDELETE /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_idandfountain_secret.Test withdrawals for both
discountandsubscriptiontoken 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_idandfountain_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.