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