diff options
Diffstat (limited to 'design-documents/046-mumimo-contracts.rst')
-rw-r--r-- | design-documents/046-mumimo-contracts.rst | 709 |
1 files changed, 709 insertions, 0 deletions
diff --git a/design-documents/046-mumimo-contracts.rst b/design-documents/046-mumimo-contracts.rst new file mode 100644 index 00000000..8cb35316 --- /dev/null +++ b/design-documents/046-mumimo-contracts.rst @@ -0,0 +1,709 @@ +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; + + // Start of the validity period of the token. + valid_after: Timestamp; + + // 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"; + + // 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 { + 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 { + 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.) |