summaryrefslogtreecommitdiff
path: root/design-documents/046-mumimo-contracts.rst
blob: 07b4c67d0b13c3f6f31f7d124b7d42963d572d08 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
DD 46: Contract Format v1
#########################

Summary
=======

The contract v1 format enables a multitude of advanced interactions between
merchants and wallets, including donations, subscriptions, coupons, currency
exchange and more.

Motivation
==========

The existing v0 contract format is too simplistic to
support many frequenly requested types of contracts.

Requirements
============

We want Taler to support various interesting use-cases:

  - Unlinkable, uncopyable subscriptions without accounts (reader can pay with
    Taler to subscribe to online publication, read unlimited number of
    articles during a certain period, transfer subscription to other devices,
    maintain unlinkability / full anonymity amongst all anonymous
    subscribers).

  - Coupons, discounts and stamps -- like receiving a discount on a product,
    product basket or subscription -- based on previous purchase(s). Again,
    with unlinkability and anonymity (modulo there being other users eligible
    for the discount).

  - Subscription tokens lost (due to loss of device without backup) should
    be recoverable from any previous backup of the subscription.

  - Currency conversion, that is exchanging one currency for another.

  - Donations, including privacy-preserving tax receipts that prove that the
    user donated to an entity that is eligible for tax-deductions but without
    revealing which entity the user donated to. At the same time, the entity
    issuing the tax receipt must be transparent (to the state) with respect to
    the amount of tax-deductable donations it has received.

  - Throttled political donations where each individual is only allowed to
    donate anonymously up to a certain amount per year or election cycle.

  - Unlinkable gifts -- enabling the purchase of digital goods (such as
    articles, albums, etc.) to be consumed by a third party. For example, a
    newspaper subscription may include a fixed number of articles that can be
    gifted to others each week, all while maintaining unlinkability and
    anonymity between the giver and the recipient.

  - Temporally-constrained, unlinkable event ticketing. Allowing visitors to
    use Taler to purchase a ticket for an event. This ticket grants entry and
    exit privileges to the event location during a specified time window, while
    preserving the anonymity of the ticket holder (within the group of all the
    ticket holders).

  - Event deposit systems. A deposit mechanism for events where customers
    receive a token alongside their cup or plate, which they are expected to
    return. This system validates that the cup or plate was legitimately
    acquired (i.e., not brought from home or stolen from a stack of dirty items)
    and incentivizes return after use.


Proposed Solution
=================

Merchants will also blindly sign tokens (not coins) to indicate the
eligibility of a user for certain special offers. Contracts will be modified
to allow requiring multiple inputs (to be *provisioned* to the merchant) and
multiple outputs (to be *yielded* by the merchant).  The wallet will then allow
the user to select between the choices that the user could pay for, or possibly
make an automatic choice if the correct choice is obvious. One output option is
blindly signed coins from another exchange, possibly in a different currency.
Another output option is blindly signed donation receipts from a DONation
AUthority (DONAU). Subscriptions can be modeled by requiring the wallet to
provision a token of the same type that is also yielded by the contract. For
security, payments using subscription tokens (and possibly certain other special
tokens?) will be limited to a list of domains explicitly defined as trusted by
the token issuer.  When paying for a contract, the wallet must additionally sign
over the selected sub-contract index and a hash committing it to the blinded
envelopes (if any).  The merchant backend will (probably?) need to be changed
to truly support multiple currencies (ugh).

.. _contract-terms-v1:

New Contract Terms Format
-------------------------

The contract terms v1 will have the following structure:

.. ts:def:: ContractTermsV1

  interface ContractTermsV1 {

    // This is version 1, the previous contract terms SHOULD
    // be indicated using "0", but in v0 specifying the version
    // is optional.
    version: "1";

    // Unique, free-form identifier for the proposal.
    // Must be unique within a merchant instance.
    // For merchants that do not store proposals in their DB
    // before the customer paid for them, the ``order_id`` can be used
    // by the frontend to restore a proposal from the information
    // encoded in it (such as a short product identifier and timestamp).
    order_id: string;

    // Price to be paid for the transaction. Could be 0.
    // The price is in addition to other instruments,
    // such as rations and tokens.
    // The exchange will subtract deposit fees from that amount
    // before transferring it to the merchant.
    price: Amount;

    // URL where the same contract could be ordered again (if
    // available). Returned also at the public order endpoint
    // for people other than the actual buyer (hence public,
    // in case order IDs are guessable).
    public_reorder_url?: string;

    // Time when this contract was generated.
    timestamp: Timestamp;

    // After this deadline, the merchant won't accept payments for the contract.
    pay_deadline: Timestamp;

    // Transfer deadline for the exchange.  Must be in the
    // deposit permissions of coins used to pay for this order.
    wire_transfer_deadline: Timestamp;

    // Merchant's public key used to sign this proposal; this information
    // is typically added by the backend. Note that this can be an ephemeral key.
    merchant_pub: EddsaPublicKey;

    // Base URL of the (public!) merchant backend API.
    // Must be an absolute URL that ends with a slash.
    merchant_base_url: string;

    // More info about the merchant (same as in v0).
    merchant: Merchant;

    // Human-readable description of the contract.
    summary: string;

    // Map from IETF BCP 47 language tags to localized summaries.
    summary_i18n?: { [lang_tag: string]: string };

    // URL that will show that the order was successful after
    // it has been paid for.  Optional. When POSTing to the
    // merchant, the placeholder "${ORDER_ID}" will be
    // replaced with the actual order ID (useful if the
    // order ID is generated server-side and needs to be
    // in the URL).
    // Note that this placeholder can only be used once.
    // Either fulfillment_url or fulfillment_message must be specified.
    fulfillment_url?: string;

    // Message shown to the customer after paying for the order.
    // Either fulfillment_url or fulfillment_message must be specified.
    fulfillment_message?: string;

    // Map from IETF BCP 47 language tags to localized fulfillment
    // messages.
    fulfillment_message_i18n?: { [lang_tag: string]: string };

    // List of products that are part of the purchase (see `Product`).
    products: Product[];

    // After this deadline has passed, no refunds will be accepted.
    refund_deadline: Timestamp;

    // Specifies for how long the wallet should try to get an
    // automatic refund for the purchase. If this field is
    // present, the wallet should wait for a few seconds after
    // the purchase and then automatically attempt to obtain
    // a refund.  The wallet should probe until "delay"
    // after the payment was successful (i.e. via long polling
    // or via explicit requests with exponential back-off).
    //
    // In particular, if the wallet is offline
    // at that time, it MUST repeat the request until it gets
    // one response from the merchant after the delay has expired.
    // If the refund is granted, the wallet MUST automatically
    // recover the payment.  This is used in case a merchant
    // knows that it might be unable to satisfy the contract and
    // desires for the wallet to attempt to get the refund without any
    // customer interaction.  Note that it is NOT an error if the
    // merchant does not grant a refund.
    auto_refund?: RelativeTime;

    // Delivery location for (all!) products (same as in v0).
    delivery_location?: Location;

    // Time indicating when the order should be delivered.
    // May be overwritten by individual products.
    delivery_date?: Timestamp;

    // Nonce generated by the wallet and echoed by the merchant
    // in this field when the proposal is generated.
    // Note: required in contract, absent in order!
    nonce: string;

    // Array of possible specific contracts the wallet/customer
    // may choose from by selecting the respective index when
    // signing the deposit confirmation.
    choices: ContractChoice[];

    // Map from token family slugs to meta data about the
    // respective token family.
    token_families: { [token_family_slug: string]: TokenFamily };

    // Extra data that is only interpreted by the merchant frontend.
    // Useful when the merchant needs to store extra information on a
    // contract without storing it separately in their database.
    extra?: any;

    // Maximum total deposit fee accepted by the merchant for this contract.
    max_fee: Amount;

    // Exchanges that the merchant accepts for this currency.
    exchanges: Exchange[];

  }

.. ts:def:: ContractChoice

  interface ContractChoice {

    // List of inputs the wallet must provision (all of them) to
    // satisfy the conditions for the contract.
    inputs: ContractInput[];

    // List of outputs the merchant promises to yield (all of them)
    // once the contract is paid.
    outputs: ContractOutput[];

  }

.. ts:def:: ContractInput

  type ContractInput =
    | ContractInputRation
    | ContractInputToken;

.. ts:def:: ContractInputRation

  interface ContractInputRation {

    type: "coin";

    // Price to be paid for the transaction.
    price: Amount;

    // FIXME-DOLD: do we want to move this into a 'details'
    // sub-structure as done with tokens below?
    class: "ration";

    // Base URL of the ration authority.
    ration_authority_url: string;

  };

.. ts:def:: ContractInputToken

  interface ContractInputToken {

    type: "token";

    // Slug of the token family in the
    // 'token_families' map on the top-level.
    token_family_slug: string;

    // Number of tokens of this type required.
    // Defaults to one if the field is not provided.
    number?: Integer;

  };

.. ts:def:: ContractOutput

  type ContractOutput =
    | ContractOutputCoin
    | ContractOutputTaxReceipt
    | ContractOutputToken;

.. ts:def:: ContractOutputCoin

  interface ContractOutputCoin {

    type: "coins";

    // Amount of coins that will be yielded.
    // This excludes any applicable withdraw fees.
    brutto_yield: Amount;

    // Base URL of the exchange that will issue the
    // coins.
    exchange_url: string;

  };

.. ts:def:: ContractOutputTaxReceipt

  interface ContractOutputTaxReceipt {

    type: "tax-receipt";

    // Base URL of the donation authority that will
    // issue the tax receipt.
    donau_url: string;

  };

.. ts:def:: ContractOutputToken

  interface ContractOutputToken {

    type: "token";

    // Slug of the token family in the
    // 'token_families' map on the top-level.
    token_family_slug: string;

    // Number of tokens to be yelded.
    // Defaults to one if the field is not provided.
    number?: Integer;

  }

.. ts:def:: TokenClass

  type TokenClass =
    | TokenClassSubscription
    | TokenClassDiscount

.. ts:def:: TokenClassSubscription

  interface TokenClassSubscription {

    class: "subscription";

    // When does the subscription period start?
    start_date: Timestamp;

    // When does the subscription period end?
    end_date: Timestamp;

    // Array of domain names where this subscription
    // can be safely used (e.g. the issuer warrants that
    // these sites will re-issue tokens of this type
    // if the respective contract says so).  May contain
    // "*" for any domain or subdomain.
    trusted_domains: string[];

  };

.. ts:def:: TokenClassDiscount

  interface TokenClassDiscount {

    class: "discount";

    // Array of domain names where this discount token
    // is intended to be used.  May contain "*" for any
    // domain or subdomain.  Users should be warned about
    // sites proposing to consume discount tokens of this
    // type that are not in this list that the merchant
    // is accepting a coupon from a competitor and thus
    // may be attaching different semantics (like get 20%
    // discount for my competitors 30% discount token).
    expected_domains: string[];

  };


.. ts:def:: TokenFamily

  interface TokenFamily {

    // Human-readable name of the token family.
    name: string;

    // Human-readable description of the semantics of
    // this token family (for display).
    description: string;

    // Map from IETF BCP 47 language tags to localized descriptions.
    description_i18n?: { [lang_tag: string]: string };

    // Public keys used to validate tokens issued by this token family.
    keys: TokenSignerPublicKeyGroup;

    // Class-specific information of the token
    details: TokenClass;

    // Must a wallet understand this token type to
    // process contracts that consume or yield it?
    critical: boolean;

    // Number of tokens issued according to ASS authority
    // FIXME: this is still rather speculative in the design...
    ass?: Integer;

    // Signature affirming sum of token issuance deposit (?) fees
    // collected by an exchange according to the ASS authority.
    // FIXME: this is still rather speculative in the design...
    ass_cost?: Amount;

    // Signature affirming the ass by the ASS authority.
    // FIXME: this is still rather speculative in the design...
    ass_sig?: EddsaSignature;

  };


.. ts:def:: TokenSignerPublicKeyGroup

  type TokenSignerPublicKeyGroup =
    | TokenSignerPublicKeyGroupRsa
    | TokenSignerPublicKeyGroupCs;

.. ts:def:: TokenSignerPublicKeyGroupRsa

  interface TokenSignerPublicKeyGroupRsa extends DenomGroupCommon {
    cipher: "RSA";

    public_keys: {
      // RSA public key.
      rsa_pub: RsaPublicKey;

      // Start time of this key's validity period.
      valid_after?: Timestamp;

      // End time of this key's validity period.
      valid_before: Timestamp;
    }[];
  }

.. ts:def:: TokenSignerPublicKeyGroupCs

  interface TokenSignerPublicKeyGroupCs extends DenomGroupCommon {
    cipher: "CS";

    public_keys: {
      // CS public key.
      cs_pub: Cs25519Point;

      // Start time of this key's validity period.
      valid_after?: Timestamp;

      // End time of this key's validity period.
      valid_before: Timestamp;
    }[];
  }


Alternative Contracts
---------------------

The contract terms object may contain any number of alternative contracts that
the user must choose between. The alternatives can differ by inputs, outputs
or other details. The wallet must filter the contracts by those that the user
can actually pay for, and move those that the user could currently not pay for
to the end of the rendered list.  Similarly, the wallet must move up the
cheaper contracts, so if a contract has a definitively lower price and
consumes an available discount token, that contract should be moved up in the
list.

Which specific alternative contract was chosen by the user is indicated in the
``choice_index`` field of the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`.


Output Commitments
------------------

When a contract has outputs, the wallet must send an array of blinded tokens,
coins or tax receipts together with the payment request.  The order in the
array must match the order in the outputs field of the contract.  For currency
outputs, one array element must include all of the required planchets for a
batch withdrawal, but of course not the reserve signature.

  .. note::

     We can probably spec this rather nicely if we first change the
     batch-withdraw API to only use a single reserve signature.

This array of blinded values is hashed to create the output commitment hash
(``h_outputs``) in the :ref:`TALER_DepositRequestPS <taler_depositrequestps>`.



Subscriptions
-------------

The user buys a subscription (and possibly at the same time an article) using
currency and the contract yields an additional subscription token as an
output.  Active subscriptions are listed below the currencies in the wallet
under a new heading.  Subscriptions are never auto-renewing, if the user wants
to extend the subscription they can trivially pay for it with one click.

When a contract consumes and yields exactly one subscription
token of the same type in a trusted domain, the wallet may automatically
approve the transaction without asking the user for confirmation (as it is free).

The token expiration for a subscription can be past the "end date" to enable a
previous subscription to be used to get a discount on renewing the
subscription.  The wallet should show applicable contracts with a lower price
that only additionally consume subscription tokens after their end date before
higher-priced alternative offers.

Subscription tokens are "critical" in that a wallet implementation must
understand them before allowing a user to interact with this class of token.
Subscription token secrets should be derived from a master secret associated
with the subscription, so that the private keys are recoverable from backup.
To obtain the blind signatures, a merchant must offer an endpoint where
one can submit the public key of the N-1 subscription token and obtain the
blinded signature over the N-th subscription token.  The wallet can then
effectively recover the subscription from backup using a binary search.

The merchant SPA should allow the administrator to create (maybe update) and
delete subscriptions. Each subscription is identified by a subscription
label and includes a validity period.

The merchant backend must then automatically manage (create, use, delete) the
respective signing keys.  When creating an order, the frontend can just refer
to the subscription label (and possibly a start date) in the inputs or
outputs. The backend should then automatically substitute this with the
respective cryptographic fields for the respective time period and
subscription label.




Discounts
---------

To offer a discount based on one or more previous purchases, a merchant must
yield some discount-specific token as an output with the previous purchase,
and then offer an alternative contract with a lower price that consumes currency
and the discount token.  The wallet should show contracts with a lower price that
only additionally consume discount tokens

The merchant SPA should allow the administrator to create (maybe update) and
delete discount tokens. Each discount token is identified by a discount
label and includes an expiration time or validity duration.

The merchant backend must then automatically manage (create, use, delete) the
respective signing keys.  When creating an order, the frontend can just refer
to the discount token label in the inputs or outputs. The backend should then
automatically substitute this with the respective cryptographic fields for the
respective discount token.


Donation Authority
------------------

A donation authority (DONAU) implements a service that is expected to be run
by a government authority that determines eligibility for tax deduction.  A
DONAU blindly signs tax receipts using a protocol very close to that of the
Taler exchange's withdraw protocol, except that the reserves are not filled
via wire transfers but instead represent accounts of the organizations
eligible to issue tax deduction receipts. These accounts are bascially
expected to have only negative balances, but the DONAU may have a
per-organization negative balance limit to cap tax deduction receipt
generation to a plausible account.  DONAU administators are expected to be
able to add, update or remove these accounts using a SPA.  Tax receipts
are blindly signed by keys that always have a usage period of one calendar
year.

A stand-alone app for tax authorities can scan QR codes representing DONAU
signatures to validate that a given tax payer has donated a certain amount.
As RSA signatures are typically very large and a single donation may require
multiple blind signatures, CS blind signatures must also be supported.  To
avoid encoding the public keys, QR codes with tax receipts should reference
the DONAU, the year and the amount, but not the specific public key.  A
single donation may nevertheless be rendered using multiple QR codes.

Revocations, refresh, deposits, age-restrictions and other exchange features
are not applicable for a DONAU.

The merchant SPA should allow the administrator to manage DONAU accounts in
the merchant backend. Each DONAU account includes a base URL and a private
signing key for signing the requests to the DONAU on behalf of the eligible
organization.

When creating an order, the frontend must specify a configured DONAU base URL
in the outputs. The backend should then automatically interact with the DONAU
when the wallet supplies the payment request with the blinded tax receipts.
The DONAU interaction must only happen after the exchange confirmed that the
contract was successfully paid.  A partial error must be returned if the
exchange interaction was successful but the DONAU interaction failed. In this
case, the fulfillment action should still be triggered, but the wallet should
display a warning that the donation receipt could not be obtained. The wallet
should then re-try the payment (in the background with an exponential
back-off) to possibly obtain the tax receipt at a later time.


Tax Receipts
------------

Tax receipts differ from coins and tokens in that what is blindly signed over
should be the taxpayer identification number of the tax payer.  The format of
the taxpayer identification number should simply be a string, with the rest
being defined by the national authority. The DONAU should indicate in its
``/config`` response what format this string should have, using possibly both
an Anastasis-style regex and an Anastasis-style function name (to check things
like checksums that cannot be validated using a regex).  Wallets must then
validate the regex (if given) and if possible should implement the
Anastasis-style logic.

Wallets should collect tax receipts by year and offer an
export functionality.  The export should generate either

    (a) a JSON file,
    (b) a PDF (with QR codes), or
    (c) a series of screens with QR codes.

Wallets may only implement some of the above options due to resource
constraints.

The documents should encode the taxpayer ID, the amount and the DONAU
signature (including the year, excluding the exact public key as there should
only be one possible).


Rationing (future work)
-----------------------

If per-capita rationing must be imposed on certain transactions, a rationing
authority (RA) must exist that identifies each eligible human and issues that
human a number of ration coins for the respective rationing period.  An RA
largely functions like a regular exchange, except that eligible humans will
need to authenticate directly to withdraw rations (instead of transferring
fiat to an exchange).  Merchants selling rationed goods will be (legally)
required to collect deposit confirmations in proportion to the amount of
rationed goods sold.  A difference to regular exchanges is that RAs do not
charge any fees.  RAs may or may not allow refreshing rations that are about
to expire for ration coins in the next period.

Once an RA is added to a wallet, it should automatically try to withdraw the
maximum amount of ration coins it is eligible for. Available rations should be
shown below the subscriptions by RA (if any).

  ..note::

    RAs are considered an idea for future work and not part of our current timeline.


Limited Donations per Capita (future work)
------------------------------------------

If per-capita limitations must be imposed on anonymous donations (for example
for donations to political parties), an RA can be used to issue donation
rations that limit the amount of donations that can be made for the respective
period.

  ..note::

    RAs are considered an idea for future work and not part of our current timeline.



Definition of Done
==================

  - Merchant backend support for multiple currencies
  - Merchant backend support for consuming and issuing tokens
  - Merchant SPA support for configuring new tokens of different types
  - Wallet-core support for various new contract types
  - Wallet-core filters for feasible contracts and possibly auto-executes subscriptions
  - Wallet-GUIs (WebEx, Android, iOS) render new contract types
  - Wallet-GUIs (WebEx, Android, iOS) allow user to select between multiple contracts
  - Documentation for developers is up-to-date
  - Token anonymity set size (ASS) authority implemented, documented
  - Merchants report anonymity set size increases to ASS authority
  - Wallets process anonymity set size reports from ASS authority
  - Bachelor thesis written on applications and design
  - Academic paper written on DONAU (requirements, design, implementation)
  - DONAU implemented, documented
  - DONAU receipt validation application implemented
  - Integration tests exist in wallet-core
  - Deliverables accepted by EC

While rationing is part of the design, we expect the actual implementation to
be done much later and thus should not consider it part of the "DONE" part.
Rationing is complex, especially as a refunded contract should probably also
refund the ration.


Alternatives
============

The first draft of this DD included the capability of paying with multiple
currencies for the same contract (for example, USD:1 and EUR:5) plus tokens
and rations. However, this is very complex, both for wallets (how to display),
for other merchant APIs (does the refund API have to become multi-currency as
well?) and there does not seem to be a good business case for it. So for now,
the price is always only in one currency.


Drawbacks
=========

Significant change, but actually good ratio compared to use-cases covered.


Discussion / Q&A
================

(This should be filled in with results from discussions on mailing lists / personal communication.)