taler-docs

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

098-token-fountains.rst (10795B)


      1 .. _dd-98:
      2 
      3 DD 98: Token Fountains for Promotions
      4 #####################################
      5 
      6 :Design status: Draft
      7 :Implementation status: Not started
      8 :DD shepherd: TBD
      9 :Historical contributors: Bohdan Potuzhnyi
     10 :First published: 2026-07-23
     11 :Last substantive change: 2026-07-24
     12 :Normative references: ``core/api-merchant.rst`` (upcoming token-fountain API)
     13 
     14 Summary
     15 =======
     16 
     17 This document proposes token fountains as a simple way for institutions to
     18 distribute promotional ``discount`` and ``subscription`` tokens.  A wallet
     19 imports a bearer credential, periodically retrieves the available grants and
     20 withdraws blind-signed tokens within limits configured by the institution.
     21 
     22 .. note::
     23 
     24   This design document proposes an extension for discussion.  The protocol
     25   version name ``vTokenFountains`` is a placeholder.
     26 
     27 Motivation
     28 ==========
     29 
     30 Institutions sometimes want to distribute promotional benefits to a group
     31 without creating and paying a separate order for every recipient.  For
     32 example, a university may give students discount tokens accepted by a campus
     33 merchant, or periodically provide subscription tokens for a continuing
     34 promotion.
     35 
     36 GNU Taler already defines blind-signed merchant tokens.  What is missing is a
     37 simple distribution channel through which a wallet can obtain an authorized
     38 set of those tokens over time.  A **token fountain** is a bearer credential
     39 that authorizes such withdrawals.
     40 
     41 The institution can distribute a fountain as a QR code or deep link.  The
     42 wallet imports the link, learns which token families are available and
     43 withdraws blind-signed tokens subject to the configured limits.  Blind
     44 signatures keep a later use of a token unlinkable from its withdrawal.
     45 
     46 User Flow
     47 =========
     48 
     49 The recipient scans the promotion once.  The wallet obtains promotional
     50 tokens and offers an applicable token later at checkout.
     51 
     52 .. uml:: images/098/promotions-user-flow.puml
     53    :align: center
     54    :width: 90%
     55    :caption: Joining and using a token-fountain promotion
     56    :alt: Scan the promotion, receive promotional tokens in the wallet, use a
     57          token at checkout and receive the discount or benefit.
     58 
     59 Requirements
     60 ============
     61 
     62 * Institutions must be able to distribute promotional tokens without
     63   creating a separate order for every withdrawal.
     64 
     65 * A fountain must support grants for existing ``discount`` and
     66   ``subscription`` token families.
     67 
     68 * Token withdrawals must use blind signatures so that later token use is not
     69   linkable to withdrawal.
     70 
     71 * The merchant backend must enforce per-period withdrawal limits atomically.
     72 
     73 * Wallets must be able to import a fountain using a QR code or Taler URI.
     74 
     75 * The bearer secret must not appear in HTTP request URLs after onboarding.
     76 
     77 * Deployments must be able to update or revoke future withdrawals without
     78   invalidating tokens that were already issued.
     79 
     80 Proposed Solution
     81 =================
     82 
     83 Terminology
     84 -----------
     85 
     86 In protocol descriptions, this document uses the existing token-family
     87 ``kind`` values:
     88 
     89 * A ``discount`` token is useful for a one-time or otherwise limited
     90   promotion.  It may be consumed without the merchant issuing a replacement.
     91 
     92 * A ``subscription`` token represents a continuing promotion.  When an order
     93   consumes it, the existing merchant token mechanism can return a replacement
     94   token envelope as an output of the selected choice.
     95 
     96 User-facing applications may call either benefit a **pass**.  The protocol
     97 continues to distinguish them by token-family kind.
     98 
     99 Fountain Credential
    100 -------------------
    101 
    102 A fountain has:
    103 
    104 * a public ``fountain_id`` identifying the fountain within a merchant
    105   instance;
    106 
    107 * a random ``fountain_secret`` used as its bearer credential;
    108 
    109 * a polling frequency; and
    110 
    111 * one or more grants describing which token families may be withdrawn and
    112   at what rate.
    113 
    114 The onboarding URI is:
    115 
    116 ::
    117 
    118   taler://fountain/$MERCHANT_HOST[/$INSTANCE_PATH]/$FOUNTAIN_ID/$FOUNTAIN_SECRET
    119 
    120 The URI may be displayed as a QR code or opened as a wallet deep link.
    121 ``fountain_id`` tells the wallet which fountain it is importing, while
    122 ``fountain_secret`` authenticates the import.  The wallet must treat the full
    123 URI as sensitive.
    124 
    125 After import, the secret must not be placed in subsequent HTTP URLs.  It is
    126 sent in the ``Authorization`` header when reading fountain information and in
    127 the request body when withdrawing tokens, as specified by the merchant API.
    128 
    129 Deployment Models
    130 -----------------
    131 
    132 An institution may use one fountain for an entire campaign.  This is simple
    133 to distribute, but every recipient shares one bearer credential and the same
    134 withdrawal limits.  A leaked link cannot be revoked for only one recipient.
    135 
    136 Alternatively, the institution may create one fountain per recipient and
    137 distribute each link through its existing student communication system.  This
    138 allows independent limits and revocation without requiring the merchant
    139 backend to store the student's real identity.  The fountain description can
    140 contain an opaque reference meaningful only to the institution.
    141 
    142 The choice is operational rather than cryptographic.  Blind token issuance
    143 prevents the merchant from linking a withdrawn token to its later use in
    144 either model.
    145 
    146 Grant Model
    147 -----------
    148 
    149 Each :ts:type:`FountainGrant` names a token family and defines:
    150 
    151 * ``tokens_per_period_limit``, the maximum number of tokens signed for one
    152   issue-key validity period;
    153 
    154 * ``tokens_per_period_stash``, the number of tokens the wallet should try to
    155   keep available for that period; and
    156 
    157 * ``key_window_size``, the number of issue-key slots into the future for
    158   which the wallet may prepare tokens.
    159 
    160 The merchant may update grants without replacing the bearer credential.
    161 Wallets periodically retrieve the current grants according to ``poll_freq``.
    162 Removing a grant prevents future withdrawals from that family, but tokens
    163 already withdrawn remain valid according to the token family's issue keys.
    164 
    165 Protocol Flow
    166 -------------
    167 
    168 1. The institution configures suitable ``discount`` or ``subscription`` token
    169    families on the merchant backend.
    170 
    171 2. It creates a fountain with grants for those token families.
    172 
    173 3. The backend returns ``fountain_id`` and ``fountain_secret`` once.  The
    174    institution forms the Taler fountain URI and gives it to the intended
    175    recipients.
    176 
    177 4. A wallet imports the URI and calls ``GET /fountain/info`` using the bearer
    178    credential.
    179 
    180 5. The wallet prepares blinded token envelopes for any grants it needs to
    181    replenish and submits them to ``POST /fountain/withdraw``.
    182 
    183 6. The backend checks the grant, issue-key window and withdrawal limit, then
    184    blind-signs the accepted envelopes.
    185 
    186 7. The wallet unblinds and stores the tokens.  Their later use follows the
    187    existing merchant token protocol.
    188 
    189 Merchant API
    190 ------------
    191 
    192 Private Management Operations
    193 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    194 
    195 The merchant API adds private operations to create, inspect, update, list and
    196 delete fountains:
    197 
    198 * ``POST /private/fountains``
    199 
    200 * ``GET /private/fountains``
    201 
    202 * ``GET /private/fountains/$FOUNTAIN_ID``
    203 
    204 * ``PATCH /private/fountains/$FOUNTAIN_ID``
    205 
    206 * ``DELETE /private/fountains/$FOUNTAIN_ID``
    207 
    208 Creation returns:
    209 
    210 .. ts:def:: FountainCreateResponse
    211 
    212   interface FountainCreateResponse {
    213     // Public identifier included in the wallet onboarding URI.
    214     fountain_id: Slug;
    215 
    216     // 256-bit bearer credential, Crockford Base32 encoded.
    217     // Returned only at creation; the backend stores only its hash.
    218     fountain_secret: string;
    219   }
    220 
    221 Wallet Operations
    222 ~~~~~~~~~~~~~~~~~
    223 
    224 ``GET /fountain/info`` returns the current grants, token-family metadata and
    225 issue public keys needed to prepare blinded envelopes.
    226 
    227 ``POST /fountain/withdraw`` accepts the bearer credential and blinded
    228 envelopes.  The request is grouped by token family and issue-key slot so that
    229 the backend can enforce each grant's limits.
    230 
    231 Deletion invalidates future requests using the credential.  It does not
    232 invalidate tokens that the wallet already withdrew.
    233 
    234 Security Considerations
    235 =======================
    236 
    237 Anyone possessing a fountain URI can withdraw under its grants.  Distribution
    238 systems must therefore protect the URI like any bearer credential.  Wallets
    239 must avoid exposing it in history, telemetry, screenshots or backups that are
    240 not intended to contain secrets.
    241 
    242 The backend stores only a hash of ``fountain_secret``.  It must rate-limit
    243 failed authentication and enforce withdrawal limits atomically to prevent
    244 concurrent requests from exceeding a grant.
    245 
    246 Fountains are not proof of student identity.  Eligibility is decided by the
    247 institution when it distributes the link.  A shared campaign link can be
    248 forwarded, while a per-recipient link can only be individually disabled after
    249 the institution learns that it was compromised.
    250 
    251 Privacy Considerations
    252 ======================
    253 
    254 Fountain descriptions should contain opaque administrative references rather
    255 than names or student identifiers.  The merchant backend does not need a
    256 recipient's identity to issue blind-signed tokens.
    257 
    258 Network metadata can still correlate a wallet's requests.  Deployments that
    259 require stronger network-level privacy need an appropriate transport or proxy;
    260 blind signatures alone do not hide IP addresses or request timing.
    261 
    262 Drawbacks
    263 =========
    264 
    265 The fountain API adds persistent bearer credentials, polling and quota state
    266 to the merchant backend.
    267 
    268 Shared campaign fountains trade administrative simplicity for coarse
    269 revocation and shared quotas.  Per-recipient fountains provide better control
    270 but require the institution to create and distribute many distinct links.
    271 
    272 Test Plan
    273 =========
    274 
    275 * Test creation, inspection, update, listing and deletion of fountains.
    276 
    277 * Test importing a URI containing both ``fountain_id`` and
    278   ``fountain_secret``.
    279 
    280 * Test withdrawals for both ``discount`` and ``subscription`` token families.
    281 
    282 * Test atomic enforcement of per-period limits under concurrent requests.
    283 
    284 * Test rejection of unknown grants, unsupported issue-key slots and invalid
    285   credentials.
    286 
    287 * Test that deleting a fountain prevents new withdrawals without invalidating
    288   tokens already held by a wallet.
    289 
    290 * Test that token use cannot be linked to its fountain withdrawal from the
    291   blind-signature transcript.
    292 
    293 Definition of Done
    294 ==================
    295 
    296 * The merchant backend implements fountain management, information and
    297   withdrawal operations.
    298 
    299 * The onboarding URI includes the merchant location, ``fountain_id`` and
    300   ``fountain_secret``.
    301 
    302 * Wallet Core can import a fountain, poll its grants, replenish tokens within
    303   the configured limits and stop when the credential is revoked.
    304 
    305 * Merchant applications can create and distribute promotional fountains
    306   without supplying recipient identities to the merchant backend.
    307 
    308 * Discount and subscription token families retain their existing consumption
    309   and replacement semantics.