taler-docs

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

commit 16e98153257e1c9bd66aa9d861294aaba87991b6
parent e70c18b550f91edc46ce2aa91ab1ff50e4644329
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Sun, 19 Jul 2026 17:28:50 +0200

[opensesame] some ideas on the door opening project

Diffstat:
Mcore/api-merchant.rst | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/merchant/delete-private-fountains-FOUNTAIN_ID.rst | 19+++++++++++++++++++
Acore/merchant/get-fountain-info.rst | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acore/merchant/get-private-fountains-FOUNTAIN_ID.rst | 35+++++++++++++++++++++++++++++++++++
Acore/merchant/get-private-fountains.rst | 37+++++++++++++++++++++++++++++++++++++
Mcore/merchant/get-private-otp-devices-DEVICE_ID.rst | 9+++++++++
Acore/merchant/patch-private-fountains-FOUNTAIN_ID.rst | 47+++++++++++++++++++++++++++++++++++++++++++++++
Acore/merchant/post-fountain-withdraw.rst | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/merchant/post-orders-ORDER_ID-pay.rst | 5+++++
Acore/merchant/post-private-fountains.rst | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/merchant/post-private-otp-devices.rst | 29++++++++++++++++++++++++++++-
Mcore/merchant/post-templates-TEMPLATE_ID.rst | 10++++++++++
Adesign-documents/097-project-sesame.rst | 1087+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adesign-documents/images/097/Makefile | 26++++++++++++++++++++++++++
Adesign-documents/images/097/sesame-business-lifecycle.puml | 21+++++++++++++++++++++
Adesign-documents/images/097/sesame-business-lifecycle.svg | 2++
Adesign-documents/images/097/sesame-business-overview.puml | 29+++++++++++++++++++++++++++++
Adesign-documents/images/097/sesame-business-overview.svg | 2++
Adesign-documents/images/097/sesame-business-signup.puml | 27+++++++++++++++++++++++++++
Adesign-documents/images/097/sesame-business-signup.svg | 2++
Adesign-documents/images/097/sesame-data-model.puml | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Adesign-documents/images/097/sesame-data-model.svg | 2++
Adesign-documents/images/097/sesame-deployment.puml | 25+++++++++++++++++++++++++
Adesign-documents/images/097/sesame-deployment.svg | 2++
Adesign-documents/images/097/sesame-door-access.puml | 35+++++++++++++++++++++++++++++++++++
Adesign-documents/images/097/sesame-door-access.svg | 2++
Adesign-documents/images/097/sesame-onboarding.puml | 21+++++++++++++++++++++
Adesign-documents/images/097/sesame-onboarding.svg | 2++
Adesign-documents/images/097/sesame-withdraw.puml | 18++++++++++++++++++
Adesign-documents/images/097/sesame-withdraw.svg | 2++
Adesign-documents/images/097/taler-style.iuml | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdesign-documents/index.rst | 1+
32 files changed, 1929 insertions(+), 1 deletion(-)

diff --git a/core/api-merchant.rst b/core/api-merchant.rst @@ -63,6 +63,10 @@ The currently implemented protocol version is **v32**. **Upcoming versions:** * ``vTAXES``: adds features to manage taxes +* ``vOpenSesame``: adds fountains for continuous token issuance, + challenge-signature OTP algorithms, token choices in fixed-order + templates and same-key token re-issue for anonymous access control + (see :doc:`DD 97 </design-documents/097-project-sesame>`) **Ideas for future version:** @@ -970,6 +974,47 @@ Deleting token families .. include:: merchant/delete-private-tokenfamilies-TOKEN_FAMILY_SLUG.rst +.. _merchant-fountain-api: + +------------------------------------- +Fountains: Continuous Token Issuance +------------------------------------- + +A fountain is a per-user bearer credential that entitles a wallet to +withdraw blind-signed tokens for a set of token families, without +creating an order per withdrawal. Fountains are the issuance channel +for anonymous access control +(see :doc:`DD 97 </design-documents/097-project-sesame>`): revoking +a user means deleting their fountain (or patching away a grant), +after which the affected tokens expire with their issue keys. + +Wallets receive the credential as a +``taler://fountain/$MERCHANT_HOST[/$INSTANCE_PATH]/$FOUNTAIN_SECRET`` +URI. All fountain endpoints are available since protocol +**vOpenSesame**. + +Managing fountains +^^^^^^^^^^^^^^^^^^ + +.. include:: merchant/post-private-fountains.rst + +.. include:: merchant/patch-private-fountains-FOUNTAIN_ID.rst + +.. include:: merchant/get-private-fountains.rst + +.. include:: merchant/get-private-fountains-FOUNTAIN_ID.rst + +.. include:: merchant/delete-private-fountains-FOUNTAIN_ID.rst + + +Wallet fountain API +^^^^^^^^^^^^^^^^^^^ + +.. include:: merchant/get-fountain-info.rst + +.. include:: merchant/post-fountain-withdraw.rst + + ----------------------- Donau Charity Instances ----------------------- @@ -1071,6 +1116,13 @@ open or deferred. // This parameter is optional. amount?: Amount; + // Contract choices for orders created from this template. + // If present, instantiated orders are version 1 contracts + // with exactly these choices (token inputs/outputs). + // Must not be combined with ``amount``. + // Since protocol **vOpenSesame**. + choices?: OrderChoice[]; + } .. ts:def:: TemplateContractInventoryCart @@ -1204,6 +1256,16 @@ Template Choices // the validity is supposed to be "now" (time of order creation). valid_at?: Timestamp; + // If true, the output tokens are blind-signed with the same + // issue key (same ``h_issue``) as the input token of the same + // token family consumed by the selected choice, instead of the + // key of the current time slot. The choice MUST then also + // contain an `OrderInputToken` for the same + // ``token_family_slug``, and this field must not be combined + // with ``valid_at``. + // Since protocol **vOpenSesame**. + reissue_with_input_key?: boolean; + } .. ts:def:: OrderOutputTaxReceipt @@ -1751,6 +1813,11 @@ and is thus not yet buyer-specific. // Index of the public key for this output token // in the `ContractTokenFamily` ``keys`` array. + // If the corresponding `OrderOutputToken` set + // ``reissue_with_input_key``, this designates the key of the + // current time slot and is overridden at pay time by the key + // matching the ``h_issue`` of the presented input token. + // Since protocol **vOpenSesame**. key_index: Integer; } diff --git a/core/merchant/delete-private-fountains-FOUNTAIN_ID.rst b/core/merchant/delete-private-fountains-FOUNTAIN_ID.rst @@ -0,0 +1,19 @@ +.. http:delete:: [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID + + Deletes a fountain: full revocation of the associated user. + Subsequent wallet requests with the fountain secret fail with + ``401 Unauthorized``; tokens already withdrawn remain valid until + their issue key expires. + This endpoint is available since protocol **vOpenSesame**. + + **Required permission:** ``fountains-write`` + + **Response:** + + :http:statuscode:`204 No content`: + The fountain was deleted. + :http:statuscode:`404 Not found`: + The fountain or merchant instance is unknown. + Returned with ``TALER_EC_MERCHANT_GENERIC_FOUNTAIN_UNKNOWN``. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. diff --git a/core/merchant/get-fountain-info.rst b/core/merchant/get-fountain-info.rst @@ -0,0 +1,60 @@ +.. http:get:: [/instances/$INSTANCE]/fountain/info + + Used by wallets to learn which token families they can withdraw + from and to obtain the token family metadata and issue public + keys needed to prepare blinded envelopes. Wallets re-poll this + endpoint at the advertised ``poll_freq`` to learn about grant + changes (added or removed groups, partial revocation). + This endpoint is available since protocol **vOpenSesame**. + + The fountain secret authenticates the request via the + ``Authorization: Bearer $FOUNTAIN_SECRET`` header; it is + deliberately never placed in the URL to keep it out of server + logs and proxies. + + **Response:** + + :http:statuscode:`200 OK`: + The backend has successfully returned the fountain information. + Returns a `FountainInfoResponse`. + :http:statuscode:`401 Unauthorized`: + The fountain secret is unknown. This includes deleted + fountains: the wallet should report the access as revoked. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainInfoResponse + + interface FountainInfoResponse { + + // How often the wallet should re-poll this endpoint. + poll_freq: RelativeTime; + + // Grants of this fountain, with the token family metadata + // and issue keys needed to prepare blinded envelopes. + grants: FountainWalletGrant[]; + } + + .. ts:def:: FountainWalletGrant + + interface FountainWalletGrant { + + // Slug of the token family. + token_family_slug: string; + + // Maximum withdrawals per key validity period. + tokens_per_period_limit: Integer; + + // Suggested number of tokens to hold per period. + tokens_per_period_stash: Integer; + + // Number of key slots ahead the wallet may withdraw for. + key_window_size: Integer; + + // Token family metadata including the currently valid + // (and, within ``key_window_size``, upcoming) issue + // public keys. + token_family: ContractTokenFamily; + } diff --git a/core/merchant/get-private-fountains-FOUNTAIN_ID.rst b/core/merchant/get-private-fountains-FOUNTAIN_ID.rst @@ -0,0 +1,35 @@ +.. http:get:: [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID + + Returns detailed information about a specific fountain. + The fountain secret is never returned. + This endpoint is available since protocol **vOpenSesame**. + + **Required permission:** ``fountains-read`` + + **Response:** + + :http:statuscode:`200 OK`: + The backend has successfully returned the fountain details. + Returns a `FountainDetails`. + :http:statuscode:`404 Not found`: + The fountain or merchant instance is unknown. + Returned with ``TALER_EC_MERCHANT_GENERIC_FOUNTAIN_UNKNOWN``. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainDetails + + interface FountainDetails { + + // Human-readable description of the fountain. + description: string; + + // How often wallets holding this fountain's secret should + // re-poll ``GET /fountain/info`` for updated grants. + poll_freq: RelativeTime; + + // Grants of this fountain. + grants: FountainGrant[]; + } diff --git a/core/merchant/get-private-fountains.rst b/core/merchant/get-private-fountains.rst @@ -0,0 +1,37 @@ +.. http:get:: [/instances/$INSTANCE]/private/fountains + + Lists all fountains of the instance. + This endpoint is available since protocol **vOpenSesame**. + + **Required permission:** ``fountains-read`` + + **Response:** + + :http:statuscode:`200 OK`: + The backend has successfully returned the list of fountains. + Returns a `FountainsListResponse`. + :http:statuscode:`404 Not found`: + The merchant instance is unknown. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainsListResponse + + interface FountainsListResponse { + + // All fountains of this instance. + fountains: FountainEntry[]; + } + + .. ts:def:: FountainEntry + + interface FountainEntry { + + // Identifier of the fountain. + fountain_id: string; + + // Human-readable description of the fountain. + description: string; + } diff --git a/core/merchant/get-private-otp-devices-DEVICE_ID.rst b/core/merchant/get-private-otp-devices-DEVICE_ID.rst @@ -45,8 +45,17 @@ // 0: None // 1: TOTP without price // 2: TOTP with price + // 3: ECDSA signature over the order's challenge + // (since protocol **vOpenSesame**) + // 4: EdDSA signature over the order's challenge + // (since protocol **vOpenSesame**) otp_algorithm: Integer; + // Public key of the device key pair, only present for + // challenge-signature algorithms (3 and 4). + // Since protocol **vOpenSesame**. + otp_device_pub?: string; + // Counter for counter-based OTP devices. otp_ctr?: Integer; diff --git a/core/merchant/patch-private-fountains-FOUNTAIN_ID.rst b/core/merchant/patch-private-fountains-FOUNTAIN_ID.rst @@ -0,0 +1,47 @@ +.. http:patch:: [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID + + Modifies a fountain. Used for group membership changes and + partial revocation: replacing the grants does not invalidate + the fountain secret; wallets learn about the changed grants on + their next ``GET /fountain/info`` poll. + This endpoint is available since protocol **vOpenSesame**. + + **Required permission:** ``fountains-write`` + + **Request:** + + The request must be a `FountainPatchRequest`. + + **Response:** + + :http:statuscode:`204 No content`: + The fountain was modified successfully. + :http:statuscode:`400 Bad Request`: + The request body is malformed. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. + :http:statuscode:`404 Not found`: + The fountain or merchant instance is unknown, or a + ``token_family_slug`` in the grants does not refer to a known + token family. + Returned with ``TALER_EC_MERCHANT_GENERIC_FOUNTAIN_UNKNOWN``. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainPatchRequest + + interface FountainPatchRequest { + + // New human-readable description. + // Optional, keeps the previous value if absent. + description?: string; + + // New poll frequency. + // Optional, keeps the previous value if absent. + poll_freq?: RelativeTime; + + // New set of grants, fully replacing the previous one. + // Optional, keeps the previous grants if absent. + grants?: FountainGrant[]; + } diff --git a/core/merchant/post-fountain-withdraw.rst b/core/merchant/post-fountain-withdraw.rst @@ -0,0 +1,95 @@ +.. http:post:: [/instances/$INSTANCE]/fountain/withdraw + + Used by wallets to withdraw blind-signed tokens for any subset of + the fountain's grants in a single request. The request is + structured per grant: each entry addresses one granted token + family and one key slot, and carries the envelopes to sign + against that grant's limits. + This endpoint is available since protocol **vOpenSesame**. + + The fountain secret travels in the request body, never in the + URL. + + **Request:** + + The request must be a `FountainWithdrawRequest`. + + **Response:** + + :http:statuscode:`200 OK`: + The tokens were blind-signed. + Returns a `FountainWithdrawResponse`. + :http:statuscode:`400 Bad Request`: + The request body is malformed. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. + :http:statuscode:`401 Unauthorized`: + The fountain secret is unknown. This includes deleted + fountains: the wallet should report the access as revoked. + :http:statuscode:`409 Conflict`: + An entry refers to a token family without a matching grant, + or its ``valid_at`` lies outside the grant's key window. + :http:statuscode:`429 Too many requests`: + The grant's ``tokens_per_period_limit`` is exhausted for the + requested slot. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainWithdrawRequest + + interface FountainWithdrawRequest { + + // The fountain's bearer credential. + fountain_secret: string; + + // One entry per (granted token family, key slot) being + // withdrawn from. At most one entry per such pair. + grants: FountainGrantWithdrawal[]; + } + + .. ts:def:: FountainGrantWithdrawal + + interface FountainGrantWithdrawal { + + // Token family to withdraw from. Must match one of the + // fountain's grants. + token_family_slug: string; + + // Desired validity time of the tokens; selects the issue + // key slot. Must lie within the grant's + // ``key_window_size``. Defaults to "now". + valid_at?: Timestamp; + + // Blinded envelopes to sign with the selected slot's issue + // key, as in the pay protocol. The number of envelopes is + // counted against the grant's ``tokens_per_period_limit`` + // for that slot. + envelopes: TokenEnvelope[]; + } + + .. ts:def:: FountainWithdrawResponse + + interface FountainWithdrawResponse { + + // One entry per entry in the request's ``grants`` array, + // in the same order. + grants: FountainGrantWithdrawalResult[]; + } + + .. ts:def:: FountainGrantWithdrawalResult + + interface FountainGrantWithdrawalResult { + + // Token family these signatures belong to. + token_family_slug: string; + + // Hash of the issue public key that was used, so the + // wallet can associate the tokens with the right slot. + h_issue: HashCode; + + // Blind signatures, in the same order as the ``envelopes`` + // of the request entry, as in the pay protocol + // (`TokenIssueBlindSig`). + blind_sigs: TokenIssueBlindSig[]; + } diff --git a/core/merchant/post-orders-ORDER_ID-pay.rst b/core/merchant/post-orders-ORDER_ID-pay.rst @@ -197,6 +197,11 @@ // Text to be shown to the point-of-sale staff as a proof of // payment. + // For OTP devices using a challenge-signature algorithm + // ("ECDSA_CHALLENGE" or "EDDSA_CHALLENGE"), contains the + // Crockford Base32 encoded signature over the hash of the + // order's challenge, made with the device private key. + // Since protocol **vOpenSesame**. pos_confirmation?: string; // Signed tokens. Returned in the same order as the diff --git a/core/merchant/post-private-fountains.rst b/core/merchant/post-private-fountains.rst @@ -0,0 +1,83 @@ +.. http:post:: [/instances/$INSTANCE]/private/fountains + + This is used to create a fountain: a per-user bearer credential + that entitles a wallet to withdraw blind-signed tokens for a set + of token families (see :doc:`DD 97 </design-documents/097-project-sesame>`). + This endpoint is available since protocol **vOpenSesame**. + + **Required permission:** ``fountains-write`` + + **Request:** + + The request must be a `FountainCreateRequest`. + + **Response:** + + :http:statuscode:`200 OK`: + The fountain was created successfully. + Returns a `FountainCreateResponse`. + :http:statuscode:`400 Bad Request`: + The request body is malformed. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. + :http:statuscode:`404 Not found`: + The merchant instance is unknown, or a ``token_family_slug`` + in the grants does not refer to a known token family. + :http:statuscode:`500 Internal Server Error`: + The server experienced an internal failure. + + **Details:** + + .. ts:def:: FountainCreateRequest + + interface FountainCreateRequest { + + // Human-readable description; typically an opaque user + // identifier of the provisioning access management system. + // The merchant backend does not need -- and should not + // receive -- the user's real identity. + description: string; + + // How often wallets holding this fountain's secret should + // re-poll ``GET /fountain/info`` for updated grants. + poll_freq: RelativeTime; + + // One grant per token family the user may withdraw + // tokens for. + grants: FountainGrant[]; + } + + .. ts:def:: FountainGrant + + interface FountainGrant { + + // Slug of the token family this grant refers to. + token_family_slug: string; + + // Maximum number of tokens the fountain will blind-sign + // per key validity period for this family. + tokens_per_period_limit: Integer; + + // Number of tokens the wallet should aim to hold per period + // (client-side stocking target; must not exceed + // ``tokens_per_period_limit``). + tokens_per_period_stash: Integer; + + // Number of key slots ahead of the current one the wallet + // may withdraw tokens for. Bounds prefetching and thereby + // the revocation latency. + key_window_size: Integer; + } + + .. ts:def:: FountainCreateResponse + + interface FountainCreateResponse { + + // Identifier of the fountain, used for subsequent + // CRUD operations. + fountain_id: string; + + // 256-bit bearer credential for the wallet, Crockford + // Base32 encoded. Returned exactly once; the backend + // stores only a hash of it. + fountain_secret: string; + } diff --git a/core/merchant/post-private-otp-devices.rst b/core/merchant/post-private-otp-devices.rst @@ -12,6 +12,12 @@ :http:statuscode:`204 No content`: The creation of the template is successful. + :http:statuscode:`200 OK`: + The device was created and uses a challenge-signature + ``otp_algorithm`` ("ECDSA_CHALLENGE" or "EDDSA_CHALLENGE"): the + backend generated the device key pair and returns a + `OtpDeviceCreateResponse` with the public key. + Since protocol **vOpenSesame**. :http:statuscode:`400 Bad Request`: The request body is malformed. :http:statuscode:`404 Not found`: @@ -47,15 +53,36 @@ // Taler base32-crockford encoding. // Instead it uses the RFC 3548 encoding to // be compatible with the TOTP standard. - otp_key: string; + // Since protocol **vOpenSesame** this field is optional: + // it MUST be omitted for the challenge-signature algorithms + // (3 and 4), where the backend generates the key pair itself + // and never exposes the private key. + otp_key?: string; // Algorithm for computing the POS confirmation. // "NONE" or 0: No algorithm (no pos confirmation will be generated) // "TOTP_WITHOUT_PRICE" or 1: Without amounts (typical OTP device) // "TOTP_WITH_PRICE" or 2: With amounts (special-purpose OTP device) + // "ECDSA_CHALLENGE" or 3: ECDSA signature over the order's + // challenge, since protocol **vOpenSesame**. + // "EDDSA_CHALLENGE" or 4: EdDSA signature over the order's + // challenge, since protocol **vOpenSesame**. // The "string" variants are supported @since protocol **v7**. otp_algorithm: Integer | string; // Counter for counter-based OTP devices. otp_ctr?: Integer; } + + .. ts:def:: OtpDeviceCreateResponse + + interface OtpDeviceCreateResponse { + + // Public key of the key pair generated by the backend for + // this device, to be configured into the offline verifier. + // For "ECDSA_CHALLENGE" a compressed NIST P-256 point, for + // "EDDSA_CHALLENGE" an Ed25519 public key. + // Crockford Base32 encoded. + // Since protocol **vOpenSesame**. + otp_device_pub: string; + } diff --git a/core/merchant/post-templates-TEMPLATE_ID.rst b/core/merchant/post-templates-TEMPLATE_ID.rst @@ -67,6 +67,16 @@ // Since protocol **v25**. tip?: Amount; + // Challenge obtained out-of-band (e.g. read from an offline + // lock over NFC): 32 bytes, Crockford Base32 encoded. Stored + // with the order and signed by the OTP device associated with + // the template when the POS confirmation is computed. Only + // meaningful -- and then mandatory -- if the template's OTP + // device uses a challenge-signature algorithm ("ECDSA_CHALLENGE" + // or "EDDSA_CHALLENGE"); rejected otherwise. + // Since protocol **vOpenSesame**. + challenge?: string; + } .. ts:def:: UsingTemplateFixedOrderRequest diff --git a/design-documents/097-project-sesame.rst b/design-documents/097-project-sesame.rst @@ -0,0 +1,1087 @@ +DD 97: Project Sesame — Anonymous Access Control +################################################ + +*Status*: draft (2026-07-17) + +Summary +======= + +This design document describes how Taler eco-system can be used +to enable anonymous access to different systems. +A wallet obtains blind-signed +tokens — one per *access group* — from the merchant backend via a new +*fountain* credential. To open a door, the wallet reads a challenge +from the (offline) lock, instantiates an order from the door's +template, pays with its group token (consuming it and receiving a +replacement via blind signature), and relays the resulting signed +confirmation (``pos_confirmation``) back to the lock, which verifies +it without any network connection. The merchant backend learns *that* +someone from a given access group opened a given door, but not *who*. + +The design reuses existing merchant features — token families, +templates, OTP devices, v1 contract choices and blind signatures — +and adds one new concept (the fountain) plus a small number of +protocol extensions. All protocol changes proposed here are +annotated with "Since protocol **vOpenSesame**"; the placeholder is +to be replaced with the concrete merchant protocol version number +once the changes are implemented. + +Motivation +========== + +Conventional electronic access control systems identify the user on +every door opening, producing detailed movement profiles. At the +same time, purely offline mechanical or RFID solutions lack +revocability, usage statistics and flexible group management. + +The Taler merchant backend already contains almost all building +blocks for a privacy-preserving alternative: + +* *Token families* provide blind-signed, single-use, time-sliced + tokens (see `DD 46 <046-mumimo-contracts.html>`__ and + `DD 68 <068-tokens-roadmap.html>`__). +* *Templates* allow order creation without merchant-side frontend + logic. +* *OTP devices* logic allows generation of confirmation + (``pos_confirmation``) for successful payments, which an offline + device (door) can verify. + +Beyond pure access control, the same infrastructure supports *paying* +to open a door (parking garage, laundry room): a template choice with +a non-zero amount is an ordinary Taler payment. + +Requirements +============ + +* **Good UX**: the user taps the door with a phone; the door opens + within well under a second. +* **Anonymity within the group**: the merchant backend learns + "someone from group X opened door Y at time T", but cannot identify + the individual user, nor link accesses across doors to the same + user. +* **Offline locks**: the lock verifies a confirmation without any + network connection. Only the wallet and the merchant backend need + Internet connectivity. +* **Per-group tokens**: one blind-signed token per access group, not + per door. A user in two groups holds two tokens regardless of the + number of doors. +* **Revocability**: revocation latency is configurable per access + group (e.g. 15 minutes for a server room, 30 days for a co-working + space). Revocation means: stop issuing tokens. +* **Usage statistics**: every door opening is recorded at the + merchant backend (door, group, timestamp) for maintenance and + reporting, without identifying users. +* **Payment support**: doors may additionally (or exclusively) be + opened against payment in digital cash. +* **IAM decoupling**: identity management stays outside the merchant + backend. Any identity/access management system can provision + fountains via the private API. +* **Minimal Taler changes**: reuse token families, templates, OTP + devices and the pay protocol; add only the fountain and small + extensions to existing endpoints. + +Proposed Solution +================= + +Business view +------------- + +This section is the non-technical summary; the protocol description +starts at `Architecture`_. + +Employees or members carry a digital key in the Taler Wallet app on +their phone. Opening a door means tapping the phone on the reader: +the phone contacts the Taler service over the Internet +and shows the door a signed permit. The doors themselves need no +network connection and no per-user configuration. + +.. figure:: images/097/sesame-business-overview.svg + :align: center + :alt: Opening a door, business view: tap the phone, the phone + trades an anonymous group pass for a signed permit, the door + checks the permit offline and opens. + + + Opening a door (business view) + +The key is anonymous within a group: access management (the door +owner) sees that some member of, say, the staff group opened a door, +which is enough for maintenance planning and billing, but cannot +tell which member it was and cannot build movement profiles. + +Getting the key is a one-time sign-up with the company account. +Access management registers a digital key for the person's access +groups and the phone receives it as a QR code or link. From then on the wallet +app picks up its passes by itself in the background. The sign-up is +personal, but the passes obtained with the key are not: the service +cannot link a pass to the sign-up it came from, which is what makes +door openings anonymous. + +.. figure:: images/097/sesame-business-signup.svg + :align: center + :alt: Sign-up, business view: sign in with the company account, + access management registers a digital key, the phone scans a + QR code and from then on picks up anonymous passes + automatically. + + + Sign-up and pass pick-up (business view) + +Access is managed per group, not per door. Group membership comes +from the existing IAM or HR system. When a person leaves, access +management stops their key with one API call; passes already on the +phone expire on their own within a period configured per area +(15 minutes for a server room, days for a co-working space). + +.. figure:: images/097/sesame-business-lifecycle.svg + :align: center + :alt: Access lifecycle, business view: person joins, phone receives + the digital key, doors open during membership, key is stopped + on leaving and remaining passes expire on their own. + + + Access lifecycle (business view) + +The same setup can also charge for opening a door (parking garage, +laundry room): a door can accept payment in digital cash instead of, +or in addition to, a group pass. + +Architecture +------------ + +.. list-table:: + :header-rows: 1 + :widths: 20 65 15 + + * - Component + - Role + - Network + * - IAM / identity provider (e.g. OIDC) + - Authenticates users towards the access control operator. + - online + * - Access control system (ACS) + - Manages doors, access groups and user→group mappings; provisions + token families, templates, OTP devices and fountains at the + merchant backend via the private API. + - online + * - Merchant backend + - Issues blind-signed tokens, verifies token payments, computes + ``pos_confirmation`` for locks. + - online + * - Taler Wallet + - Holds fountain secret and tokens; bridges lock and merchant + backend. + - online + * - Lock + - Generates challenges, verifies ``pos_confirmation``, opens the + door. + - offline + +.. figure:: images/097/sesame-deployment.svg + :align: center + :alt: Deployment view: wallet bridges the offline lock and the + merchant backend; the ACS provisions the merchant backend and + configures locks out-of-band. + + + Deployment view + +Cryptographic building blocks +----------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 40 30 + + * - Primitive + - Purpose + - Instantiation + * - Blind signature + - Token issuance (withdrawal and re-issue) + - existing Taler token family code (RSA/CS) + * - Signature over challenge (``pos_confirmation``) + - Merchant confirmation to the lock + - ECDSA (P-256) or EdDSA, **new** + * - TOTP + - Door challenge / replay prevention + - existing OTP devices + +Data model mapping +------------------ + +.. figure:: images/097/sesame-data-model.svg + :align: center + :alt: Data model mapping: access group to token family (1:1), door + to template plus OTP device (1:1:1), user to fountain (1:1); + template choices and fountain grants reference token + families. + + + Data model mapping + +A door with a distinct access policy needs its own door id (its own +OTP device and template). Doors MAY share one door id if their +access policy is identical, at the cost of losing per-door usage +statistics. + +Access groups as token families +------------------------------- + +Each access group is an ordinary token family created via +:http:post:`[/instances/$INSTANCE]/private/tokenfamilies` with +``kind: "subscription"`` — no new token family kind is introduced. +The merchant backend already auto-generates one issue key per +``validity_granularity`` time slot; the hash of the issue key +(``h_issue``) thus identifies group and time slot. Different groups +can use different ``duration`` values: + +* server room: 15 minutes, +* office building: 1 day, +* co-working space: 30 days. + +The token ``duration`` bounds the revocation latency (see +`Revocation`_). + +Doors as OTP devices and templates +---------------------------------- + +Each door is registered as an OTP device +(:http:post:`[/instances/$INSTANCE]/private/otp-devices`) and gets a +template (:http:post:`[/instances/$INSTANCE]/private/templates`) +whose ``otp_id`` references the device. The ``otp_algorithm`` +determines how the merchant backend computes the +``pos_confirmation`` that the lock verifies: + +===== ========================= ====================================================== +Value Name ``pos_confirmation`` +===== ========================= ====================================================== +0 ``NONE`` none (existing) +1 ``TOTP_WITHOUT_PRICE`` TOTP code, shared-secret verification (existing) +2 ``TOTP_WITH_PRICE`` TOTP code including price (existing) +3 ``ECDSA_CHALLENGE`` ECDSA signature over the order's challenge + (**new**, since protocol **vOpenSesame**) +4 ``EDDSA_CHALLENGE`` EdDSA signature over the order's challenge + (**new**, since protocol **vOpenSesame**) +===== ========================= ====================================================== + +For the challenge-signature algorithms (3 and 4) the key pair is +generated *by the merchant backend*; the private key is never +exposed. The public key is returned on device creation and the ACS +configures it into the lock. For TOTP algorithms the lock shares the +``otp_key`` secret as today. + +The lock itself is configured out-of-band (not stored at the +merchant) with: door id, challenge mode (TOTP or nonce), and either +the TOTP secret or the device public key. Challenge handling at the +lock: + +========= ==================== ==================================================== +Mode Lock generates Lock verifies +========= ==================== ==================================================== +TOTP nothing recomputes TOTP code with its own clock +Nonce random 32 bytes signature verification of ``pos_confirmation`` + over its own nonce with the device public key +========= ==================== ==================================================== + +The door template ties the device to the access groups allowed to +open it. Since protocol **vOpenSesame**, fixed-order templates can +carry v1 contract ``choices`` (see `Extended: templates with token +choices`_). Example (informal): + +.. code-block:: none + + TemplateCreateRequest { + template_id: "door-201", + template_description: "Office building room 201", + otp_id: "door-201", + template_contract: { + choices: [ + { // staff members: consume one token, get a replacement + amount: "KUDOS:0", + inputs: [ { type: "token", token_family_slug: "staff" } ], + outputs: [ { type: "token", token_family_slug: "staff", + reissue_with_input_key: true } ] + }, + { // facilities team: different group, same door + amount: "KUDOS:0", + inputs: [ { type: "token", token_family_slug: "facilities" } ], + outputs: [ { type: "token", token_family_slug: "facilities", + reissue_with_input_key: true } ] + }, + { // visitors: pay to open + amount: "EUR:5" + } + ] + } + } + +Multiple choices mean multiple access groups (or payment options) for +the same door; the wallet picks the choice matching a token family it +holds. Each token choice consumes one token (input) and re-issues +one (output) — the single-use replacement mechanism. A one-time +access pass (visitor) is simply a choice *without* a token output, +paired with a ``discount``-kind token family; no separate mechanism +is needed. + +Same-key constraint +~~~~~~~~~~~~~~~~~~~ + +When a choice consumes and re-issues a token of the *same* family, +the replacement token must be signed with the *same* issue key as the +consumed token — not with the key of the current time slot. This is +what the new ``reissue_with_input_key`` flag on `OrderOutputToken` +expresses. + +Without this constraint, a revoked user could keep obtaining +replacement tokens under ever-newer keys through door accesses alone, +bypassing the fountain (where revocation is enforced) indefinitely. +With the constraint, the replacement expires together with the +consumed token, forcing every wallet back to the fountain once per +key slot. + +Users as fountains +------------------ + +A *fountain* is a per-user bearer credential at the merchant backend +that entitles a wallet to withdraw blind-signed tokens for a set of +token families. It is the only new concept introduced by this +design. + +* One fountain per user, created by the ACS at user onboarding. +* Each fountain holds a 256-bit ``fountain_secret`` (given to the + user's wallet) and a list of *grants*, one per access group. +* Adding/removing group memberships: ``PATCH`` the fountain's grants; + the secret stays valid. +* Revoking the user entirely: ``DELETE`` the fountain. + +The wallet receives the credential as a new URI (an extension of +`LSD 0006 <https://lsd.gnunet.org/lsd0006/>`__ is required): + +.. code-block:: none + + taler://fountain/$MERCHANT_HOST[/$INSTANCE_PATH]/$FOUNTAIN_SECRET + +Each grant carries three parameters: + +* ``tokens_per_period_limit``: how many tokens the fountain will + blind-sign per key validity period (rate limit; relevant for + non-subscription families such as one-time passes). +* ``tokens_per_period_stash``: how many tokens the wallet *should* + hold per period (client-side target). +* ``key_window_size``: how many key slots ahead of the current one + the wallet may withdraw for. With window size 3 and a duration of + one day, the wallet can prefetch tokens for today, tomorrow and the + day after, reducing fountain round trips — at the cost of + revocation latency (see `Revocation`_). + +Protocol flows +-------------- + +System setup +~~~~~~~~~~~~ + +Performed by the ACS through the private merchant API: + +1. For each access group: + :http:post:`[/instances/$INSTANCE]/private/tokenfamilies` + (``kind: "subscription"``, per-group ``duration`` and + ``validity_granularity``). +2. For each door: + :http:post:`[/instances/$INSTANCE]/private/otp-devices` with + ``otp_algorithm`` 1 (TOTP lock) or 3/4 (challenge-signature lock). + For 3/4 the response contains the generated device public key. +3. For each door: + :http:post:`[/instances/$INSTANCE]/private/templates` with + ``otp_id`` = device id and one contract choice per authorized + group. +4. The ACS configures the physical lock with door id, challenge mode + and the TOTP secret or device public key. + +User onboarding +~~~~~~~~~~~~~~~ + +1. The user authenticates towards the ACS via the IAM (e.g. OIDC). +2. The ACS resolves the user's roles to access groups. +3. The ACS creates the fountain: + ``POST /private/fountains`` with one grant per group. +4. The ACS hands the resulting ``taler://fountain/...`` URI to the + user's wallet (QR code, deep link, ...). +5. The wallet stores the fountain URI, fetches the fountain info and + withdraws its initial tokens (next section). + +.. figure:: images/097/sesame-onboarding.svg + :align: center + :alt: Onboarding sequence: IAM login, ACS resolves groups, creates + the fountain at the merchant backend, hands the fountain URI + to the wallet. + + + User onboarding + +Token withdrawal via fountain +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The fountain secret is never placed in a URL (it would leak into +server logs and proxies): it travels in an ``Authorization: Bearer`` +header for GET and in the request body for POST. + +.. figure:: images/097/sesame-withdraw.svg + :align: center + :alt: Withdrawal sequence: wallet fetches grants and issue keys via + GET /fountain/info, then submits blinded envelopes via + POST /fountain/withdraw and unblinds the returned signatures. + + + Token withdrawal via fountain + +The wallet re-polls ``GET /fountain/info`` at the advertised +``poll_freq`` to learn about grant changes (added or removed groups). + +Offline door access +~~~~~~~~~~~~~~~~~~~ + +.. figure:: images/097/sesame-door-access.svg + :align: center + :alt: Door access sequence: lock hands the challenge to the wallet + over NFC, the wallet creates and pays an order at the merchant + backend with its group token, and presents the resulting + pos_confirmation to the lock. + + + Offline door access + +The two HTTP round trips (order creation and payment) dominate the +door opening latency; the cryptography (ECDSA verification on the +lock MCU with hardware acceleration) is on the order of 15 ms. + +Token renewal +~~~~~~~~~~~~~ + +Tokens are replaced inline on every door access, but all tokens of a +slot expire together with the slot's issue key (enforced by the +same-key constraint). Before expiry — with a grace period of, e.g., +a quarter of the validity period — the wallet performs a regular +fountain withdrawal (previous section) using background scheduling +(Android WorkManager / iOS BGTaskScheduler). If background execution +is unavailable, renewal happens on next app start, possibly making +the user wait briefly at the door. + +Revocation +~~~~~~~~~~ + +Because tokens are blind-signed, the merchant backend cannot +selectively invalidate a token it has never seen. Revocation +therefore means *stop issuing*: + +* **Full revocation**: ``DELETE /private/fountains/$FOUNTAIN_ID``. + The wallet's next fountain request fails with 401; existing tokens + die with their issue key. +* **Partial revocation** (user leaves one group): + ``PATCH /private/fountains/$FOUNTAIN_ID`` with the remaining + grants. The secret stays valid; the wallet learns about the + reduced grants on its next ``GET /fountain/info`` poll and drops + the affected tokens. + +The maximum revocation delay is ``duration`` × ``key_window_size``: +prefetched tokens for future slots remain usable until their keys +expire. Operators choose these parameters per group accordingly. +Other users of the group are unaffected. The same-key constraint is +what makes this bound hold (see `Same-key constraint`_). + +Usage statistics +~~~~~~~~~~~~~~~~ + +No new mechanism: the existing ``pay`` webhook +(`WebhookEventType`) fires on every successful door opening and +carries the paid contract terms and order id, from which the ACS +derives door id, token family and timestamp. + +Protocol changes (vOpenSesame) +------------------------------ + +Endpoints used unchanged +~~~~~~~~~~~~~~~~~~~~~~~~ + +* :http:post:`[/instances/$INSTANCE]/private/tokenfamilies` — + create access group. +* :http:get:`[/instances/$INSTANCE]/templates/$TEMPLATE_ID` — + wallet fetches template details. +* :http:post:`[/instances/$INSTANCE]/orders/$ORDER_ID/claim` and + :http:post:`[/instances/$INSTANCE]/orders/$ORDER_ID/pay` — token + spend/re-issue via the existing `PayRequest` / `PaymentResponse` + structures (``tokens``, ``wallet_data.tokens_evs``, + ``token_sigs``, ``pos_confirmation``). +* Webhooks (``pay`` event) for usage statistics. + +Extended: challenge-signature OTP devices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:http:post:`[/instances/$INSTANCE]/private/otp-devices` accepts two +new ``otp_algorithm`` values and, for these, generates the key pair +itself: + +.. ts:def:: OtpDeviceAddDetails + + interface OtpDeviceAddDetails { + + // Device ID to use. + otp_device_id: string; + + // Human-readable description for the device. + otp_device_description: string; + + // A key encoded with RFC 3548 Base32. + // IMPORTANT: This is not using the typical + // Taler base32-crockford encoding. + // Instead it uses the RFC 3548 encoding to + // be compatible with the TOTP standard. + // Since protocol **vOpenSesame** this field is optional: + // it MUST be omitted for the challenge-signature + // algorithms (3 and 4), where the backend generates + // the key pair and never exposes the private key. + otp_key?: string; + + // Algorithm for computing the POS confirmation. + // "NONE" or 0: No algorithm (no pos confirmation will be generated) + // "TOTP_WITHOUT_PRICE" or 1: Without amounts (typical OTP device) + // "TOTP_WITH_PRICE" or 2: With amounts (special-purpose OTP device) + // "ECDSA_CHALLENGE" or 3: ECDSA signature over the order's + // challenge. Since protocol **vOpenSesame**. + // "EDDSA_CHALLENGE" or 4: EdDSA signature over the order's + // challenge. Since protocol **vOpenSesame**. + otp_algorithm: Integer | string; + + // Counter for counter-based OTP devices. + otp_ctr?: Integer; + } + +For algorithms 3 and 4 the response changes from ``204 No content`` +to ``200 OK`` with: + +.. ts:def:: OtpDeviceCreateResponse + + interface OtpDeviceCreateResponse { + + // Public key of the key pair generated by the backend + // for this device, to be configured into the offline + // verifier (lock). For "ECDSA_CHALLENGE" a compressed + // NIST P-256 point, for "EDDSA_CHALLENGE" an Ed25519 + // public key. Crockford Base32 encoded. + // Since protocol **vOpenSesame**. + otp_device_pub: string; + } + +`OtpDeviceDetails` (returned by +:http:get:`[/instances/$INSTANCE]/private/otp-devices/$DEVICE_ID`) +gains the same field: + +.. code-block:: none + + // Public key of the device key pair, present only for + // challenge-signature algorithms (3 and 4). + // Since protocol **vOpenSesame**. + otp_device_pub?: string; + +The corresponding C enum ``TALER_MerchantConfirmationAlgorithm`` +(today ``TALER_MCA_NONE`` = 0, ``TALER_MCA_WITHOUT_PRICE`` = 1, +``TALER_MCA_WITH_PRICE`` = 2) is extended with +``TALER_MCA_ECDSA_CHALLENGE`` = 3 and +``TALER_MCA_EDDSA_CHALLENGE`` = 4, and +``TALER_build_pos_confirmation()`` learns to sign the hash of the +order's challenge with the device private key. In the +`PaymentResponse`, ``pos_confirmation`` then carries the Crockford +Base32 encoded signature; the JSON schema is unchanged. + +Extended: templates with token choices +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Fixed-order templates learn to describe v1 contracts. +`TemplateContractFixedOrder` is extended: + +.. ts:def:: TemplateContractFixedOrder + + interface TemplateContractFixedOrder { + + // The price is imposed by the merchant and cannot be + // changed by the customer. + // This parameter is optional. + amount?: Amount; + + // Contract choices for orders created from this + // template. If present, instantiated orders are + // version 1 contracts with exactly these choices + // (token inputs/outputs, see `OrderChoice`), enabling + // token-gated and token-re-issuing templates. + // Must not be combined with ``amount``. + // Since protocol **vOpenSesame**. + choices?: OrderChoice[]; + } + +Extended: same-key re-issue constraint +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`OrderOutputToken` gains a flag that binds the issue key of an output +token to the input token consumed in the same choice: + +.. ts:def:: OrderOutputToken + + interface OrderOutputToken { + + // Token output. + type: "token"; + + // Token family slug as configured in the merchant backend. + token_family_slug: string; + + // How many units of the output are issued by the merchant. + // Defaults to 1 if not specified. + count?: Integer; + + // When should the output token be valid. + valid_at?: Timestamp; + + // If true, the output tokens are blind-signed with the + // same issue key (same ``h_issue``) as the input token + // of the same token family consumed by this choice, + // instead of the key of the current time slot. The + // choice MUST then also contain an `OrderInputToken` + // for the same ``token_family_slug``, and this field + // must not be combined with ``valid_at``. + // Since protocol **vOpenSesame**. + reissue_with_input_key?: boolean; + } + +At pay time, :http:post:`[/instances/$INSTANCE]/orders/$ORDER_ID/pay` +selects the issue key matching the ``h_issue`` of the presented input +token for such outputs. The effective key MUST be one of the keys +listed for the family in the contract's `ContractTokenFamily` +``keys`` array; the ``key_index`` in `ContractOutputToken` designates +the key for the current slot and is overridden by this rule (see +`Discussion / Q&A`_). + +Extended: challenge pass-through on template use +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:http:post:`[/instances/$INSTANCE]/templates/$TEMPLATE_ID` accepts a +client-supplied challenge that the backend stores with the order and +later signs when computing the ``pos_confirmation``: + +.. ts:def:: UsingTemplateCommonRequest + + interface UsingTemplateCommonRequest { + + // Type of the template being instantiated. + template_type: string; + + // Summary to use in the contract. Only if + // not already specified by the template. + summary?: string; + + // The amount to be paid, including tip. + amount?: Amount; + + // Optional tip amount. + tip?: Amount; + + // Challenge obtained out-of-band (e.g. read from an + // offline lock over NFC): 32 bytes, Crockford Base32 + // encoded. Only meaningful — and then mandatory — if + // the template's OTP device uses a challenge-signature + // algorithm (3 or 4); rejected otherwise. + // Since protocol **vOpenSesame**. + challenge?: string; + } + +New: fountain management (private API) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +All fountain endpoints are new since protocol **vOpenSesame**. + +``POST [/instances/$INSTANCE]/private/fountains`` + +Creates a fountain. Request: + +.. ts:def:: FountainCreateRequest + + interface FountainCreateRequest { + + // Human-readable description; typically an opaque user + // identifier of the provisioning access control system. + // Note that the merchant backend does not need — and + // should not receive — the user's real identity. + description: string; + + // How often wallets holding this fountain's secret + // should re-poll ``GET /fountain/info`` for updated + // grants. + poll_freq: RelativeTime; + + // One grant per token family (access group) the user + // may withdraw tokens for. + grants: FountainGrant[]; + } + +.. ts:def:: FountainGrant + + interface FountainGrant { + + // Slug of the token family this grant refers to. + token_family_slug: string; + + // Maximum number of tokens the fountain will + // blind-sign per key validity period for this family. + tokens_per_period_limit: Integer; + + // Number of tokens the wallet should aim to hold per + // period (client-side stocking target; must not + // exceed ``tokens_per_period_limit``). + tokens_per_period_stash: Integer; + + // Number of key slots ahead of the current one the + // wallet may withdraw tokens for. Bounds prefetching + // and thereby the revocation latency. + key_window_size: Integer; + } + +Response (``200 OK``): + +.. ts:def:: FountainCreateResponse + + interface FountainCreateResponse { + + // Identifier of the fountain, used by the access + // control system for subsequent CRUD operations. + fountain_id: string; + + // 256-bit bearer credential for the wallet, + // Crockford Base32 encoded. Returned exactly once; + // the backend stores only a hash of it. + fountain_secret: string; + } + +``GET [/instances/$INSTANCE]/private/fountains`` + +Lists all fountains of the instance (``fountain_id`` and +``description`` per entry, following the conventions of the other +private list endpoints). + +``GET [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID`` + +Returns the fountain's ``description``, ``poll_freq`` and ``grants`` +(never the secret). + +``PATCH [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID`` + +Replaces ``description``, ``poll_freq`` and/or ``grants``. Used for +group membership changes and partial revocation; the fountain secret +remains valid. + +``DELETE [/instances/$INSTANCE]/private/fountains/$FOUNTAIN_ID`` + +Deletes the fountain: full revocation of the user. Subsequent wallet +requests with the secret fail with ``401 Unauthorized``. + +New: fountain wallet API (public) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``GET [/instances/$INSTANCE]/fountain/info`` + +Authenticated with ``Authorization: Bearer $FOUNTAIN_SECRET`` (the +secret is deliberately kept out of URLs to avoid leakage into logs +and proxies). Response (``200 OK``): + +.. ts:def:: FountainInfoResponse + + interface FountainInfoResponse { + + // How often the wallet should re-poll this endpoint. + poll_freq: RelativeTime; + + // Grants of this fountain, enriched with the token + // family metadata and issue keys the wallet needs to + // prepare blinded envelopes. + grants: FountainWalletGrant[]; + } + +.. ts:def:: FountainWalletGrant + + interface FountainWalletGrant { + + // Slug of the token family. + token_family_slug: string; + + // Maximum withdrawals per key validity period. + tokens_per_period_limit: Integer; + + // Suggested number of tokens to hold per period. + tokens_per_period_stash: Integer; + + // Number of key slots ahead the wallet may withdraw for. + key_window_size: Integer; + + // Token family metadata including the currently valid + // (and, within ``key_window_size``, upcoming) issue + // public keys. + token_family: ContractTokenFamily; + } + +Error responses: ``401 Unauthorized`` if the secret is unknown +(including deleted fountains — the wallet then reports the access as +revoked). + +``POST [/instances/$INSTANCE]/fountain/withdraw`` + +Withdraws blind-signed tokens for any subset of the fountain's +grants in a single request. The request is structured per grant: +each entry addresses one granted token family and one key slot, and +carries the envelopes to sign against that grant's limits: + +.. ts:def:: FountainWithdrawRequest + + interface FountainWithdrawRequest { + + // The fountain's bearer credential. + fountain_secret: string; + + // One entry per (granted token family, key slot) + // being withdrawn from. At most one entry per such + // pair. + grants: FountainGrantWithdrawal[]; + } + +.. ts:def:: FountainGrantWithdrawal + + interface FountainGrantWithdrawal { + + // Token family to withdraw from. Must match one of + // the fountain's grants. + token_family_slug: string; + + // Desired validity time of the tokens; selects the + // issue key slot. Must lie within the grant's + // ``key_window_size``. Defaults to "now". + valid_at?: Timestamp; + + // Blinded envelopes to sign with the selected slot's + // issue key, as in the pay protocol. The number of + // envelopes is counted against the grant's + // ``tokens_per_period_limit`` for that slot. + envelopes: TokenEnvelope[]; + } + +Response (``200 OK``): + +.. ts:def:: FountainWithdrawResponse + + interface FountainWithdrawResponse { + + // One entry per entry in the request's ``grants`` + // array, in the same order. + grants: FountainGrantWithdrawalResult[]; + } + +.. ts:def:: FountainGrantWithdrawalResult + + interface FountainGrantWithdrawalResult { + + // Token family these signatures belong to. + token_family_slug: string; + + // Hash of the issue public key that was used, so the + // wallet can associate the tokens with the right slot. + h_issue: HashCode; + + // Blind signatures, in the same order as the + // ``envelopes`` of the request entry, as in the pay + // protocol (`TokenIssueBlindSig`). + blind_sigs: TokenIssueBlindSig[]; + } + +Error responses: ``401 Unauthorized`` (unknown secret), +``409 Conflict`` (entry for a family without grant, or ``valid_at`` +outside the grant's key window), +``429 Too Many Requests`` (``tokens_per_period_limit`` exhausted for +a grant and slot). + +New URI scheme +~~~~~~~~~~~~~~ + +``taler://fountain/$MERCHANT_HOST[/$INSTANCE_PATH]/$FOUNTAIN_SECRET`` +tells a wallet where to find the fountain and how to authenticate. +This requires an extension of LSD 0006 and corresponding +wallet-core support (new transaction/subscription type holding the +fountain state, background renewal scheduling). + +Security considerations +----------------------- + +Threat model +~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 22 25 53 + + * - Attacker + - Goal + - Prevented by + * - NFC eavesdropper + - replay an access response + - single-use challenge (nonce discarded, TOTP expires) + * - wallet of wrong group + - open unauthorized door + - template choices name specific token families; pay fails for + tokens of other families + * - revoked wallet + - keep access after revocation + - same-key constraint: re-issued tokens die with the original + key; re-withdrawal requires the fountain, which is gone + * - token thief + - use a stolen token + - tokens are single-use; the legitimate user's next access + fails, exposing the theft + * - colluding doors + - track a user across doors + - lock only sees ``pos_confirmation`` for its own challenge; + blind signatures unlink accesses + * - compromised lock + - forge confirmations + - lock holds only the public key (or TOTP secret for + low-security doors); device private key stays at the backend + * - ACS + merchant collusion + - deanonymize a user + - blind signatures unlink issuance from use; the merchant knows + only "someone from group X" + * - fountain secret thief + - withdraw tokens + - equivalent to a stolen physical key; fountain can be deleted + and re-created with a new secret + +Privacy properties +~~~~~~~~~~~~~~~~~~ + +* **Lock** sees only ``pos_confirmation`` and time — no user or + group information. +* **Merchant backend** sees at withdrawal only the fountain secret + (no identity); at door access only door id, token family and time. + Blind signatures prevent linking a door access to any withdrawal + event or to other accesses by the same user. +* **Anonymity set**: all users holding valid tokens of the same + family (per time slot). +* **ACS** knows the identity→fountain mapping but never sees door + accesses of individual users. +* **Residual risk — timing correlation**: withdrawal events + (authenticated by fountain secret) and door accesses could be + correlated by timing in backend logs. Inline replacement tokens + are not withdrawals, so the correlation window is limited to the + periodic per-slot renewal; wallets SHOULD randomize renewal times + within the grace period. + +Test Plan +========= + +* Merchant backend integration tests covering: challenge-signature + OTP device lifecycle (create → key returned → pay → + ``pos_confirmation`` verifies against the returned public key); + templates with token choices; same-key re-issue (replacement token + verifiably signed by the input token's key, including across slot + boundaries); fountain CRUD, grant limits, window enforcement, and + revocation semantics (401 after DELETE, reduced grants after + PATCH). +* Wallet-core integration test: full cycle fountain URI → info → + withdraw → template order with challenge → pay with token → + unblind replacement → renewal after key expiry. +* Lock-side verification tested against a software lock simulator + (nonce and TOTP modes) before hardware integration. + +Definition of Done +================== + +* Protocol changes ratified and merged into the merchant API + reference (``core/api-merchant.rst``) with the final protocol + version number replacing **vOpenSesame**. +* Merchant backend implements the extended and new endpoints, + guarded by the protocol version bump. +* LSD 0006 extension for ``taler://fountain`` published. +* Wallet-core implements fountain handling and background renewal. +* End-to-end demo with at least one nonce-mode (challenge-signature) + and one TOTP-mode door. + +Alternatives +============ + +* **Per-door token families** instead of per-group: simpler template + choices, but token count and fountain traffic grow with the number + of doors, and the anonymity set shrinks per door. +* **Online locks**: the merchant backend (or ACS) opens the door + directly over the network, removing one NFC round trip. Rejected + as baseline: requires connected locks; may be revisited as an + optimization (see Discussion). +* **Selling access tokens as ordinary orders** (existing + subscription purchase flow) instead of fountains: works without new + endpoints, but provides no per-user revocable issuance channel, no + per-period limits, and requires a payment interaction for what is + an entitlement, complicating IAM integration. +* **Exchange coins instead of merchant tokens**: would provide + anonymity but has no notion of merchant-scoped groups, per-slot + keys or free re-issue, and would entangle access control with real + money flows. + +Drawbacks +========= + +* **Revocation latency** is bounded, not zero: up to ``duration`` × + ``key_window_size``. Short slots (server room) mean more frequent + wallet renewals and more issue keys. +* **Two HTTP round trips** per door opening put the latency budget + at the mercy of the phone's network conditions (concrete/metal + buildings, Wi-Fi↔mobile handover); see Discussion. +* **Background renewal** depends on OS task scheduling; users who + block background activity may wait at the door. +* **New credential class** (fountain secret) at the merchant backend + increases the authentication surface of the public API. +* The merchant backend accumulates per-group, per-door usage data; + operators must treat these logs as personal-data-adjacent even + though they name no user. + +Discussion / Q&A +================ + +* **Same-key constraint mechanics.** Binding the output key to the + input token (``reissue_with_input_key``) was chosen over "output + key must equal current slot" because a payment near a slot + boundary may legitimately consume a token of the previous slot. + Open question: how to represent the overridden ``key_index`` in + the contract terms cleanly — the current proposal keeps + ``key_index`` pointing at the current-slot key and overrides it at + pay time, which is implicit; an explicit marker value may be + preferable. +* **Latency budget.** Estimated door opening time is ~250–550 ms, + dominated by the two HTTP round trips (order creation + pay), not + by cryptography (P-256 verification on an MCU with hardware + acceleration is ~15 ms). These numbers need real measurements. + If they disappoint, candidate optimizations: pre-created orders + per door, combining order creation and payment into one request, + or the online-lock mode below. +* **Wallet template prefetch.** The wallet only learns which token + families a door accepts from the (cacheable) template. A reverse + lookup ("which door ids accept family X") would let the wallet + skip order creation for doors it cannot open, but requires + distributing door lists to wallets and keeping them fresh; + deferred for marginal UX gain. +* **Future work (v2):** + + * *HOTP counter mode* for clockless offline doors: the merchant + maintains a monotonic counter per device and returns + ``{ ctr, HOTP(secret, ctr) }``; the lock accepts iff + ``ctr > last_seen_ctr``. Note that ``otp_ctr`` is already + stored by the backend today but unused by + ``TALER_build_pos_confirmation()``, so this needs new + generation logic. + * *Forced key rotation* (e.g. + ``POST /private/tokenfamilies/$SLUG/rotate-keys``) for immediate + revocation, at the cost of forcing all wallets of the group to + re-withdraw. + * *Online door mode*: backend-initiated opening via a callback to + the operator's system, trading offline capability for lower + interaction latency. diff --git a/design-documents/images/097/Makefile b/design-documents/images/097/Makefile @@ -0,0 +1,26 @@ +# Regenerates the DD 97 (Project Sesame) diagrams from the +# PlantUML sources. The generated SVGs are committed, so the +# normal documentation build does not need PlantUML; run this +# only after editing a .puml file. +# +# Needs either a `plantuml` binary in $PATH, or Java plus +# PLANTUML_JAR pointing to a plantuml.jar: +# +# make diagrams +# make diagrams PLANTUML_JAR=/path/to/plantuml.jar + +PUML = $(wildcard sesame-*.puml) +SVG = $(PUML:.puml=.svg) + +ifneq ($(PLANTUML_JAR),) +PLANTUML = java -jar $(PLANTUML_JAR) +else +PLANTUML = plantuml +endif + +diagrams: $(SVG) + +%.svg: %.puml + $(PLANTUML) -tsvg $< + +.PHONY: diagrams diff --git a/design-documents/images/097/sesame-business-lifecycle.puml b/design-documents/images/097/sesame-business-lifecycle.puml @@ -0,0 +1,21 @@ +@startuml sesame-business-lifecycle +' Business view: access lifecycle (DD 97, Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml + +start +:person joins; +note right: HR / IAM assigns\naccess groups +:phone receives a digital key +(one anonymous pass per group); +repeat + :tap door, door opens; + note right: used pass is replaced\nautomatically, no user action +repeat while (still a member?) is (yes) +-> no; +:access management stops the key +(one API call); +:remaining passes expire on their own; +note right: configurable per area:\n15 minutes (server room)\nup to days (co-working) +stop +@enduml diff --git a/design-documents/images/097/sesame-business-lifecycle.svg b/design-documents/images/097/sesame-business-lifecycle.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="507px" preserveAspectRatio="none" style="width:477px;height:507px;background:#FFFFFF;" version="1.1" viewBox="0 0 477 507" width="477px" zoomAndPan="magnify"><defs/><g><ellipse cx="134.2295" cy="20" fill="#0042B3" rx="10" ry="10" style="stroke:#222222;stroke-width:1;"/><path d="M200.6367,46.8516 L200.6367,62.9844 L180.6367,66.9844 L200.6367,70.9844 L200.6367,87.1172 A0,0 0 0 0 200.6367,87.1172 L329.5532,87.1172 A0,0 0 0 0 329.5532,87.1172 L329.5532,56.8516 L319.5532,46.8516 L200.6367,46.8516 A0,0 0 0 0 200.6367,46.8516 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M319.5532,46.8516 L319.5532,56.8516 L329.5532,56.8516 L319.5532,46.8516 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="107.9165" x="206.6367" y="63.9185">HR / IAM assigns</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92.752" x="206.6367" y="79.0513">access groups</text><rect fill="#D3DEFF" height="33.9688" rx="12.5" ry="12.5" style="stroke:#0042B3;stroke-width:0.5;" width="92.8145" x="87.8223" y="50"/><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="72.8145" x="97.8223" y="71.1387">person joins</text><rect fill="#D3DEFF" height="47.9375" rx="12.5" ry="12.5" style="stroke:#0042B3;stroke-width:0.5;" width="219.3711" x="24.5439" y="107.1172"/><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="169.2598" x="34.5439" y="128.2559">phone receives a digital key</text><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="199.3711" x="34.5439" y="142.2246">(one anonymous pass per group)</text><path d="M227.0303,215.9063 L227.0303,232.0391 L207.0303,236.0391 L227.0303,240.0391 L227.0303,256.1719 A0,0 0 0 0 227.0303,256.1719 L437.749,256.1719 A0,0 0 0 0 437.749,256.1719 L437.749,225.9063 L427.749,215.9063 L227.0303,215.9063 A0,0 0 0 0 227.0303,215.9063 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M427.749,215.9063 L427.749,225.9063 L437.749,225.9063 L427.749,215.9063 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="140.3784" x="233.0303" y="232.9731">used pass is replaced</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="189.7188" x="233.0303" y="248.106">automatically, no user action</text><rect fill="#D3DEFF" height="33.9688" rx="12.5" ry="12.5" style="stroke:#0042B3;stroke-width:0.5;" width="145.6016" x="61.4287" y="219.0547"/><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="125.6016" x="71.4287" y="240.1934">tap door, door opens</text><polygon fill="#D9E3F9" points="134.2295,175.0547,146.2295,187.0547,134.2295,199.0547,122.2295,187.0547,134.2295,175.0547" style="stroke:#586A88;stroke-width:0.5;"/><polygon fill="#D9E3F9" points="91.6018,276.1719,176.8572,276.1719,188.8572,288.1719,176.8572,300.1719,91.6018,300.1719,79.6018,288.1719,91.6018,276.1719" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="85.2554" x="91.6018" y="291.98">still a member?</text><text fill="#00134A" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="19.0083" x="188.8572" y="285.5776">yes</text><rect fill="#D3DEFF" height="47.9375" rx="12.5" ry="12.5" style="stroke:#0042B3;stroke-width:0.5;" width="229.373" x="19.543" y="341.5708"/><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="209.373" x="29.543" y="362.7095">access management stops the key</text><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="78.9258" x="29.543" y="376.6782">(one API call)</text><path d="M277.459,399.5083 L277.459,423.2075 L257.459,427.2075 L277.459,431.2075 L277.459,454.9067 A0,0 0 0 0 277.459,454.9067 L465.231,454.9067 A0,0 0 0 0 465.231,454.9067 L465.231,409.5083 L455.231,399.5083 L277.459,399.5083 A0,0 0 0 0 277.459,399.5083 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M455.231,399.5083 L455.231,409.5083 L465.231,409.5083 L455.231,399.5083 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="144.7075" x="283.459" y="416.5752">configurable per area:</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="166.772" x="283.459" y="431.708">15 minutes (server room)</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="154.1211" x="283.459" y="446.8408">up to days (co-working)</text><rect fill="#D3DEFF" height="33.9688" rx="12.5" ry="12.5" style="stroke:#0042B3;stroke-width:0.5;" width="246.459" x="11" y="410.2231"/><text fill="#00134A" font-family="sans-serif" font-size="12" lengthAdjust="spacing" textLength="226.459" x="21" y="431.3618">remaining passes expire on their own</text><ellipse cx="134.2295" cy="485.9067" fill="none" rx="11" ry="11" style="stroke:#222222;stroke-width:1;"/><ellipse cx="134.2295" cy="485.9067" fill="#222222" rx="6" ry="6" style="stroke:#222222;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="30" y2="50"/><polygon fill="#0042B3" points="130.2295,40,134.2295,50,138.2295,40,134.2295,44" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="83.9688" y2="107.1172"/><polygon fill="#0042B3" points="130.2295,97.1172,134.2295,107.1172,138.2295,97.1172,134.2295,101.1172" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="199.0547" y2="219.0547"/><polygon fill="#0042B3" points="130.2295,209.0547,134.2295,219.0547,138.2295,209.0547,134.2295,213.0547" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="188.8572" x2="449.749" y1="288.1719" y2="288.1719"/><polygon fill="#0042B3" points="445.749,246.0391,449.749,236.0391,453.749,246.0391,449.749,242.0391" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="449.749" x2="449.749" y1="187.0547" y2="288.1719"/><line style="stroke:#0042B3;stroke-width:1;" x1="449.749" x2="146.2295" y1="187.0547" y2="187.0547"/><polygon fill="#0042B3" points="156.2295,183.0547,146.2295,187.0547,156.2295,191.0547,152.2295,187.0547" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="253.0234" y2="276.1719"/><polygon fill="#0042B3" points="130.2295,266.1719,134.2295,276.1719,138.2295,266.1719,134.2295,270.1719" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="155.0547" y2="175.0547"/><polygon fill="#0042B3" points="130.2295,165.0547,134.2295,175.0547,138.2295,165.0547,134.2295,169.0547" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="300.1719" y2="341.5708"/><polygon fill="#0042B3" points="130.2295,331.5708,134.2295,341.5708,138.2295,331.5708,134.2295,335.5708" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="11" lengthAdjust="spacing" textLength="13.7017" x="138.2295" y="321.4766">no</text><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="389.5083" y2="410.2231"/><polygon fill="#0042B3" points="130.2295,400.2231,134.2295,410.2231,138.2295,400.2231,134.2295,404.2231" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="134.2295" x2="134.2295" y1="444.1919" y2="474.9067"/><polygon fill="#0042B3" points="130.2295,464.9067,134.2295,474.9067,138.2295,464.9067,134.2295,468.9067" style="stroke:#0042B3;stroke-width:1;"/><!--SRC=[LL1DRzim3BthLt2dnaESOOTXc0DiQD53UnXGTDTSM9brc4gK8ChE_Ez7fG6sNGHGZu_lumRkPcKXLLYOpWEWzsq8_4f-zO5WStyFtxvkuQca4_aAlqWnKhTpD_1C4maLh0HdhiS1JhWWz2Sia0DAdMFOjQ6lkaoW5sB_SPVjttrWyM4U2Ie6AhtMDT2E6-AqOgbko5Gq2PmIY-wT95CfF1th00_Fy0aURty2gl8aUf2hwwcaEQjHZqbicppnGWe88qziGl16gziq32N96jEiaEq8cDOxkTkxGfcmEe2XOeOnfRAzl90oDIS0_tgPbSRt6wocQBazZMPehYbYPOyXh5jZjCLYxLPESfM0yv5RmLev13CPARvG-T6rIvkLj7FzTsFktN2D5r5meaXIGMlA2lL8VoFTFZr2axC8Gw68B2pJnPfLGByp5mBhqpXiMSxoVwS-oIjFSy4NizJgm48u7EJp5uWiSxKR6qkm659IYjr1vWmrmOYh8JxrvrJUJB1ppTeV]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-business-overview.puml b/design-documents/images/097/sesame-business-overview.puml @@ -0,0 +1,29 @@ +@startuml sesame-business-overview +' Business view: opening a door (DD 97, Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml + +actor "Employee\nwith phone" as emp +participant "Door" as door +participant "Taler service" as svc + +emp -> door : tap phone on the door +door --> emp : one-time code +emp -> svc : use one anonymous group pass\n+ the door's one-time code +svc -> svc : pass is valid, unused, and\nright group for this door? +svc --> emp : signed door-opening permit\n+ fresh replacement pass +emp -> door : show permit +door -> door : check permit\n(no network needed) +door --> emp : door opens + +note over svc + records "someone from group X + at door Y" — never who. + access management (door owner) + gets these usage statistics +end note + +note over emp, door + typical total: well under a second +end note +@enduml diff --git a/design-documents/images/097/sesame-business-overview.svg b/design-documents/images/097/sesame-business-overview.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="649px" preserveAspectRatio="none" style="width:548px;height:649px;background:#FFFFFF;" version="1.1" viewBox="0 0 548 649" width="548px" zoomAndPan="magnify"><defs/><g><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="46" x2="46" y1="97.5938" y2="552.8516"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="215.7065" x2="215.7065" y1="97.5938" y2="552.8516"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="365.6777" x2="365.6777" y1="97.5938" y2="552.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="69.3369" x="9.3135" y="77.9951">Employee</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="77.9639" x="5" y="94.292">with phone</text><ellipse cx="46.9819" cy="13.5" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M46.9819,21.5 L46.9819,48.5 M33.9819,29.5 L59.9819,29.5 M46.9819,48.5 L33.9819,63.5 M46.9819,48.5 L59.9819,63.5 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="69.3369" x="9.3135" y="564.8467">Employee</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="77.9639" x="5" y="581.1436">with phone</text><ellipse cx="46.9819" cy="592.9453" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M46.9819,600.9453 L46.9819,627.9453 M33.9819,608.9453 L59.9819,608.9453 M46.9819,627.9453 L33.9819,642.9453 M46.9819,627.9453 L59.9819,642.9453 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="47.667" x="192.7065" y="66.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="33.667" x="199.7065" y="86.292">Door</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="47.667" x="192.7065" y="551.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="33.667" x="199.7065" y="571.8467">Door</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="103.9883" x="314.6777" y="66.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89.9883" x="321.6777" y="86.292">Taler service</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="103.9883" x="314.6777" y="551.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89.9883" x="321.6777" y="571.8467">Taler service</text><polygon fill="#0042B3" points="204.54,124.7266,214.54,128.7266,204.54,132.7266,208.54,128.7266" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="46.9819" x2="210.54" y1="128.7266" y2="128.7266"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="145.5581" x="53.9819" y="123.6606">tap phone on the door</text><polygon fill="#0042B3" points="57.9819,153.8594,47.9819,157.8594,57.9819,161.8594,53.9819,157.8594" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="51.9819" x2="215.54" y1="157.8594" y2="157.8594"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="93.7358" x="63.9819" y="152.7935">one-time code</text><polygon fill="#0042B3" points="354.6719,198.125,364.6719,202.125,354.6719,206.125,358.6719,202.125" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="46.9819" x2="360.6719" y1="202.125" y2="202.125"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="207.2573" x="53.9819" y="181.9263">use one anonymous group pass</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="178.2104" x="53.9819" y="197.0591">+ the door's one-time code</text><line style="stroke:#0042B3;stroke-width:1;" x1="366.6719" x2="408.6719" y1="246.3906" y2="246.3906"/><line style="stroke:#0042B3;stroke-width:1;" x1="408.6719" x2="408.6719" y1="246.3906" y2="259.3906"/><line style="stroke:#0042B3;stroke-width:1;" x1="367.6719" x2="408.6719" y1="259.3906" y2="259.3906"/><polygon fill="#0042B3" points="377.6719,255.3906,367.6719,259.3906,377.6719,263.3906,373.6719,259.3906" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="168.2764" x="373.6719" y="226.1919">pass is valid, unused, and</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="163.1157" x="373.6719" y="241.3247">right group for this door?</text><polygon fill="#0042B3" points="57.9819,299.6563,47.9819,303.6563,57.9819,307.6563,53.9819,303.6563" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="51.9819" x2="365.6719" y1="303.6563" y2="303.6563"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="181.0986" x="63.9819" y="283.4575">signed door-opening permit</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="168.3018" x="63.9819" y="298.5903">+ fresh replacement pass</text><polygon fill="#0042B3" points="204.54,328.7891,214.54,332.7891,204.54,336.7891,208.54,332.7891" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="46.9819" x2="210.54" y1="332.7891" y2="332.7891"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="80.6978" x="53.9819" y="327.7231">show permit</text><line style="stroke:#0042B3;stroke-width:1;" x1="216.54" x2="258.54" y1="377.0547" y2="377.0547"/><line style="stroke:#0042B3;stroke-width:1;" x1="258.54" x2="258.54" y1="377.0547" y2="390.0547"/><line style="stroke:#0042B3;stroke-width:1;" x1="217.54" x2="258.54" y1="390.0547" y2="390.0547"/><polygon fill="#0042B3" points="227.54,386.0547,217.54,390.0547,227.54,394.0547,223.54,390.0547" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="85.1602" x="223.54" y="356.856">check permit</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="136.1318" x="223.54" y="371.9888">(no network needed)</text><polygon fill="#0042B3" points="57.9819,415.1875,47.9819,419.1875,57.9819,423.1875,53.9819,419.1875" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="51.9819" x2="215.54" y1="419.1875" y2="419.1875"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="72.8521" x="63.9819" y="414.1216">door opens</text><path d="M244,432.1875 L244,502.1875 L488,502.1875 L488,442.1875 L478,432.1875 L244,432.1875 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M478,432.1875 L478,442.1875 L488,442.1875 L478,432.1875 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="208.3872" x="250" y="449.2544">records "someone from group X</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="158.3804" x="250" y="464.3872">at door Y" &#8212; never who.</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="223.1011" x="250" y="479.52">access management (door owner)</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173.7988" x="250" y="494.6528">gets these usage statistics</text><path d="M13,512.7188 L13,537.7188 L250,537.7188 L250,522.7188 L240,512.7188 L13,512.7188 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M240,512.7188 L240,522.7188 L250,522.7188 L240,512.7188 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="216.4043" x="19" y="529.7856">typical total: well under a second</text><!--SRC=[NPA_Zjim3CPtFmNh9KaRUosQuGeKkVtGTcY1B8B4sCfPf23ADhBr8Vg4VP9ISSveCndoH_xuySy6lepY2KLWyZWVW2EIfmuCEEO4s-CHFdtSmqlY2zeCtr1Cm5rRRU0hTaYOJ4QOVUuFS36JWUPY4iJ1K1x3q9P7asNgG6w9pViswl_gdISxZ0uXcm5J8_awOEkBKXcRjMxz7EB0LyGJ5JZ4dWbhC08OOXLDojxwg5MWFghFcr8C_ozzBtGjdIPlbsoPR5Kf0fgdfS63MeWB7fWWzxXmRcAZKILOnqBOP1yGB3jy0oXCfL5A9e8XfclWKQ1BF2hJY9pemuhSo0EbfA-K4WrUjs06x_OmacBrQyYTAFckptVgMMtbtY_TVbuOgqln7Q6xIStR8YEcu7CnSaueFIJKxLWCg0CgHH-c8JtFzvpx35R9zcXVLzwM60ZppEbLl-ZGxHw7TdiM7r9Ln7edF9Lrw0f0RLXEJg0M3bZ6TquSxZt-KDta9Vrd3Nz-_TOA9NNkkIsQjULWWo7JBOribrAptkDE0phCKkQkcnb5Oq2ooLxqCBHTSb3C_6j9xUwNjGFaQ_JM391PR_C0Cmw3RiDfbD5BiankHVm5]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-business-signup.puml b/design-documents/images/097/sesame-business-signup.puml @@ -0,0 +1,27 @@ +@startuml sesame-business-signup +' Business view: sign-up and pass pick-up (DD 97, Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml + +actor "Employee\nwith phone" as emp +participant "Access management\n(door owner, HR/IT)" as mgmt +participant "Taler service" as svc + +emp -> mgmt : sign in with company account\n(single sign-on) +mgmt -> mgmt : look up the person's\naccess groups +mgmt -> svc : register a digital key\nfor these groups +svc --> mgmt : key credential +mgmt --> emp : QR code / link +emp -> emp : scan with the\nTaler Wallet app + +group pass pick-up (automatic, repeats in background) + emp -> svc : present key credential + svc --> emp : anonymous passes\n(one per group) +end + +note over emp, svc + the sign-up is personal, the passes are not: + the service cannot tell which passes + belong to whom (blind signatures) +end note +@enduml diff --git a/design-documents/images/097/sesame-business-signup.svg b/design-documents/images/097/sesame-business-signup.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="645px" preserveAspectRatio="none" style="width:506px;height:645px;background:#FFFFFF;" version="1.1" viewBox="0 0 506 645" width="506px" zoomAndPan="magnify"><defs/><g><rect fill="#FFFFFF" height="90.5313" style="stroke:#586A88;stroke-width:1.5;" width="490.9243" x="10" y="375.9219"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="61" x2="61" y1="97.5938" y2="548.8516"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="280.3223" x2="280.3223" y1="97.5938" y2="548.8516"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="437.936" x2="437.936" y1="97.5938" y2="548.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="69.3369" x="24.3135" y="77.9951">Employee</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="77.9639" x="20" y="94.292">with phone</text><ellipse cx="61.9819" cy="13.5" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M61.9819,21.5 L61.9819,48.5 M48.9819,29.5 L74.9819,29.5 M61.9819,48.5 L48.9819,63.5 M61.9819,48.5 L74.9819,63.5 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="69.3369" x="24.3135" y="560.8467">Employee</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="77.9639" x="20" y="577.1436">with phone</text><ellipse cx="61.9819" cy="588.9453" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M61.9819,596.9453 L61.9819,623.9453 M48.9819,604.9453 L74.9819,604.9453 M61.9819,623.9453 L48.9819,638.9453 M61.9819,623.9453 L74.9819,638.9453 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="160.4053" x="200.3223" y="50"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="146.4053" x="207.3223" y="69.9951">Access management</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="136.958" x="212.0459" y="86.292">(door owner, HR/IT)</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="160.4053" x="200.3223" y="547.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="146.4053" x="207.3223" y="567.8467">Access management</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="136.958" x="212.0459" y="584.1436">(door owner, HR/IT)</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="103.9883" x="386.936" y="66.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89.9883" x="393.936" y="86.292">Taler service</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="103.9883" x="386.936" y="547.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89.9883" x="393.936" y="567.8467">Taler service</text><polygon fill="#0042B3" points="268.5249,139.8594,278.5249,143.8594,268.5249,147.8594,272.5249,143.8594" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="61.9819" x2="274.5249" y1="143.8594" y2="143.8594"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="194.543" x="68.9819" y="123.6606">sign in with company account</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="100.5215" x="68.9819" y="138.7935">(single sign-on)</text><line style="stroke:#0042B3;stroke-width:1;" x1="280.5249" x2="322.5249" y1="188.125" y2="188.125"/><line style="stroke:#0042B3;stroke-width:1;" x1="322.5249" x2="322.5249" y1="188.125" y2="201.125"/><line style="stroke:#0042B3;stroke-width:1;" x1="281.5249" x2="322.5249" y1="201.125" y2="201.125"/><polygon fill="#0042B3" points="291.5249,197.125,281.5249,201.125,291.5249,205.125,287.5249,201.125" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="132.1772" x="287.5249" y="167.9263">look up the person's</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92.752" x="287.5249" y="183.0591">access groups</text><polygon fill="#0042B3" points="426.9302,241.3906,436.9302,245.3906,426.9302,249.3906,430.9302,245.3906" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="280.5249" x2="432.9302" y1="245.3906" y2="245.3906"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="134.4053" x="287.5249" y="225.1919">register a digital key</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="107.0596" x="287.5249" y="240.3247">for these groups</text><polygon fill="#0042B3" points="291.5249,270.5234,281.5249,274.5234,291.5249,278.5234,287.5249,274.5234" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="285.5249" x2="437.9302" y1="274.5234" y2="274.5234"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92.6187" x="297.5249" y="269.4575">key credential</text><polygon fill="#0042B3" points="72.9819,299.6563,62.9819,303.6563,72.9819,307.6563,68.9819,303.6563" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="66.9819" x2="279.5249" y1="303.6563" y2="303.6563"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="90.3843" x="78.9819" y="298.5903">QR code / link</text><line style="stroke:#0042B3;stroke-width:1;" x1="61.9819" x2="103.9819" y1="347.9219" y2="347.9219"/><line style="stroke:#0042B3;stroke-width:1;" x1="103.9819" x2="103.9819" y1="347.9219" y2="360.9219"/><line style="stroke:#0042B3;stroke-width:1;" x1="62.9819" x2="103.9819" y1="360.9219" y2="360.9219"/><polygon fill="#0042B3" points="72.9819,356.9219,62.9819,360.9219,72.9819,364.9219,68.9819,360.9219" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="87.3057" x="68.9819" y="327.7231">scan with the</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="106.7358" x="68.9819" y="342.856">Taler Wallet app</text><path d="M10,375.9219 L412.6206,375.9219 L412.6206,383.0547 L402.6206,393.0547 L10,393.0547 L10,375.9219 " fill="#D9E3F9" style="stroke:#586A88;stroke-width:1.5;"/><rect fill="none" height="90.5313" style="stroke:#586A88;stroke-width:1.5;" width="490.9243" x="10" y="375.9219"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="357.6206" x="25" y="388.9888">pass pick-up (automatic, repeats in background)</text><polygon fill="#0042B3" points="426.9302,410.1875,436.9302,414.1875,426.9302,418.1875,430.9302,414.1875" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="61.9819" x2="432.9302" y1="414.1875" y2="414.1875"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="146.4531" x="68.9819" y="409.1216">present key credential</text><polygon fill="#0042B3" points="72.9819,454.4531,62.9819,458.4531,72.9819,462.4531,68.9819,458.4531" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="66.9819" x2="437.9302" y1="458.4531" y2="458.4531"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="124.3887" x="78.9819" y="438.2544">anonymous passes</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="102.2354" x="78.9819" y="453.3872">(one per group)</text><path d="M17,478.4531 L17,533.4531 L482,533.4531 L482,488.4531 L472,478.4531 L17,478.4531 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><path d="M472,478.4531 L472,488.4531 L482,488.4531 L472,478.4531 " fill="#F1F1F4" style="stroke:#586A88;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="281.8232" x="104.5505" y="495.52">the sign-up is personal, the passes are not:</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="235.9297" x="104.5505" y="510.6528">the service cannot tell which passes</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="223.7803" x="104.5505" y="525.7856">belong to whom (blind signatures)</text><!--SRC=[NPBDZXCn48JlVeeYboGeapqYSa02BHBS8Ap49PUEfvauyPzipqHvUzgUX1Lx7Bkhg-hpBF5bpCPppfWCNtV8Pl3T646-HoGvZaPVwi7g-HaVFspm8uKpwu9VdCdnUgkMsFF0dXCLnjMKqmvdcWZTcHAY9Lz6PxVreojv6f2Ri7k_ZNAktXclxTWp2bbENIutorjJRnJf4X8MNrsquSPyy7KvuYbuNe0os4KLAHMZJHGNB3vhNOiuyZImOryEVjK7sH6k4c-3R_kdxo_hfdM3A_-BNwg_X4kJqVF-F6cbn0JTfpQFcGwCRpMXWnFf3QHr69kPa1mipmo3NwkcUbNR42uGbEN4Y9noyCjyy3Id7b8OO_ud4NDH91vCBfABq9l122DS-7RmVwIKRCdyaDNnxjL9Xg0JzyB0aBqlbUlQPeUVUyako9zWZRyyAivtMTEzdkm_-9dARxAM2oX6fPhXcr-3nX8S2SkD98vC9LT8HzANEktxjGBkBdElc2IxK7-J4tZqcCEG3_xcmfYR7mkibJn-PJVtNYlslL8-o9yN9ZaLsQQz6nhanyziyfqtsSrClwq39OQ8Tu_n-V4X2EGKXQt5zMJqwJulOqUsmGyeGIw2m-ee1FlcGsMKKYrHtSb_0G00]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-data-model.puml b/design-documents/images/097/sesame-data-model.puml @@ -0,0 +1,50 @@ +@startuml sesame-data-model +' Data model mapping for DD 97 (Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml +skinparam classAttributeIconSize 0 +hide circle +hide methods +left to right direction + +package "Access control system" { + class "access group" as group { + group id + group expiry + } + class "door" as door { + door id + door groups + } + class "user" as user { + user groups + credential + } +} + +package "Merchant backend (Taler)" { + class "token family" as tf { + slug + duration, validity_granularity + } + class "template" as tpl { + template_id + contract choices: one per group + } + class "OTP device" as otp { + otp_device_id + } + class "fountain" as fountain { + grants: one per group + fountain_secret: held by wallet + } +} + +group ..> tf : 1:1 +door ..> tpl : 1:1 (+ OTP device) +user ..> fountain : 1:1 + +tpl --> otp : otp_id = template_id +tpl --> tf : choice inputs/outputs\nper group +fountain --> tf : one grant per group +@enduml diff --git a/design-documents/images/097/sesame-data-model.svg b/design-documents/images/097/sesame-data-model.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="341px" preserveAspectRatio="none" style="width:1046px;height:341px;background:#FFFFFF;" version="1.1" viewBox="0 0 1046 341" width="1046px" zoomAndPan="magnify"><defs/><g><!--cluster Access control system--><g id="cluster_Access control system"><path d="M8.5,11 L185.3066,11 A3.75,3.75 0 0 1 187.8066,13.5 L194.8066,33.2969 L194.5,33.2969 A2.5,2.5 0 0 1 197,35.7969 L197,331.5 A2.5,2.5 0 0 1 194.5,334 L8.5,334 A2.5,2.5 0 0 1 6,331.5 L6,13.5 A2.5,2.5 0 0 1 8.5,11 " fill="#FFFFFF" style="stroke:#586A88;stroke-width:1.5;"/><line style="stroke:#586A88;stroke-width:1.5;" x1="6" x2="194.8066" y1="33.2969" y2="33.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="175.8066" x="10" y="25.9951">Access control system</text></g><!--cluster Merchant backend (Taler)--><g id="cluster_Merchant backend (Taler)"><path d="M356.5,6 L561.5049,6 A3.75,3.75 0 0 1 564.0049,8.5 L571.0049,28.2969 L1036.5,28.2969 A2.5,2.5 0 0 1 1039,30.7969 L1039,235.5 A2.5,2.5 0 0 1 1036.5,238 L356.5,238 A2.5,2.5 0 0 1 354,235.5 L354,8.5 A2.5,2.5 0 0 1 356.5,6 " fill="#FFFFFF" style="stroke:#586A88;stroke-width:1.5;"/><line style="stroke:#586A88;stroke-width:1.5;" x1="354" x2="571.0049" y1="28.2969" y2="28.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="204.0049" x="358" y="20.9951">Merchant backend (Taler)</text></g><!--class group--><g id="elem_group"><rect codeLine="10" fill="#FFFFFF" height="66.8906" id="group" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="101.1338" x="51" y="250.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="92.5928" x="55.2705" y="268.4951">access group</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="52" x2="151.1338" y1="276.7969" y2="276.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="58.1943" x="57" y="293.792">group id</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89.1338" x="57" y="310.0889">group expiry</text></g><!--class door--><g id="elem_door"><rect codeLine="14" fill="#FFFFFF" height="66.8906" id="door" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="96.4854" x="53.5" y="46.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="31.7734" x="85.856" y="64.4951">door</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="54.5" x2="148.9854" y1="72.7969" y2="72.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="49" x="59.5" y="89.792">door id</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.4854" x="59.5" y="106.0889">door groups</text></g><!--class user--><g id="elem_user"><rect codeLine="18" fill="#FFFFFF" height="66.8906" id="user" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="95.248" x="54" y="148.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="30.5361" x="86.356" y="166.4951">user</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="55" x2="148.248" y1="174.7969" y2="174.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="83.248" x="60" y="191.792">user groups</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="70.2871" x="60" y="208.0889">credential</text></g><!--class tf--><g id="elem_tf"><rect codeLine="25" fill="#FFFFFF" height="66.8906" id="tf" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="214.8633" x="808" y="153.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="87.3086" x="871.7773" y="171.4951">token family</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="809" x2="1021.8633" y1="179.7969" y2="179.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="28.9434" x="814" y="196.792">slug</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="202.8633" x="814" y="213.0889">duration, validity_granularity</text></g><!--class tpl--><g id="elem_tpl"><rect codeLine="29" fill="#FFFFFF" height="66.8906" id="tpl" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="235.5693" x="370" y="46.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="63.1982" x="456.1855" y="64.4951">template</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="371" x2="604.5693" y1="72.7969" y2="72.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="82.9746" x="376" y="89.792">template_id</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="223.5693" x="376" y="106.0889">contract choices: one per group</text></g><!--class otp--><g id="elem_otp"><rect codeLine="33" fill="#FFFFFF" height="50.5938" id="otp" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="107.7031" x="861.5" y="41.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="78.4492" x="876.127" y="59.4951">OTP device</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="862.5" x2="968.2031" y1="67.7969" y2="67.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="95.7031" x="867.5" y="84.792">otp_device_id</text></g><!--class fountain--><g id="elem_fountain"><rect codeLine="36" fill="#FFFFFF" height="66.8906" id="fountain" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="227.9473" x="374" y="151.5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="58.0713" x="458.938" y="169.4951">fountain</text><line style="stroke:#0042B3;stroke-width:0.5;" x1="375" x2="600.9473" y1="177.7969" y2="177.7969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="153.2207" x="380" y="194.792">grants: one per group</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="215.9473" x="380" y="211.0889">fountain_secret: held by wallet</text></g><!--link group to tf--><g id="link_group_tf"><path codeLine="42" d="M152.05,283.4 C242.61,281.75 440.71,275.64 606,253 C673.69,243.73 742.829,229.192 801.919,215.312 " fill="none" id="group-to-tf" style="stroke:#0042B3;stroke-width:1;stroke-dasharray:7.0,7.0;"/><polygon fill="#0042B3" points="807.76,213.94,798.0838,212.104,802.8925,215.0834,799.9132,219.8921,807.76,213.94" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="20.9219" x="478" y="249.0669">1:1</text></g><!--link door to tpl--><g id="link_door_tpl"><path codeLine="43" d="M149.65,80 C204.09,80 289.66,80 363.66,80 " fill="none" id="door-to-tpl" style="stroke:#0042B3;stroke-width:1;stroke-dasharray:7.0,7.0;"/><polygon fill="#0042B3" points="369.66,80,360.66,76,364.66,80,360.66,84,369.66,80" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="123.0684" x="214" y="76.0669">1:1 (+ OTP device)</text></g><!--link user to fountain--><g id="link_user_fountain"><path codeLine="44" d="M149.26,182.36 C204.7,182.8 293.0102,183.4827 367.9002,184.0727 " fill="none" id="user-to-fountain" style="stroke:#0042B3;stroke-width:1;stroke-dasharray:7.0,7.0;"/><polygon fill="#0042B3" points="373.9,184.12,364.9318,180.0492,368.9002,184.0806,364.8688,188.049,373.9,184.12" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="20.9219" x="265.5" y="179.0669">1:1</text></g><!--link tpl to otp--><g id="link_tpl_otp"><path codeLine="46" d="M606.11,76.42 C689.51,73.87 791.6628,70.753 855.2328,68.813 " fill="none" id="tpl-to-otp" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="861.23,68.63,852.1122,64.9064,856.2323,68.7825,852.3562,72.9027,861.23,68.63" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="135.8716" x="639.5" y="68.0669">otp_id = template_id</text></g><!--link tpl to tf--><g id="link_tpl_tf"><path codeLine="47" d="M606.11,109.45 C669.21,125.32 740.6711,143.287 802.0811,158.727 " fill="none" id="tpl-to-tf" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="807.9,160.19,800.147,154.1162,803.0509,158.9708,798.1963,161.8747,807.9,160.19" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="140.4736" x="637" y="99.0669">choice inputs/outputs</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="63.7686" x="675.3525" y="114.1997">per group</text></g><!--link fountain to tf--><g id="link_fountain_tf"><path codeLine="48" d="M602.15,189.24 C613.58,189.55 625.02,189.81 636,190 C699.1,191.07 714.89,190.69 778,190 C787.72,189.89 791.8208,189.8407 801.9508,189.6707 " fill="none" id="fountain-to-tf" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="807.95,189.57,798.8841,185.7216,802.9507,189.6539,799.0184,193.7205,807.95,189.57" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="131.1235" x="641.5" y="186.0669">one grant per group</text></g><!--SRC=[RLF1Rjim3BtxAxXSccopjvwA6LY10hdiCAnOUnmGC19ZAvKbGwBIUKF_VR9aEmcmavyeFfBlKRw1BJ92PoLfwB3lbMdWO1rij_3b3jQFpXv9C3oHnuusLN43FwaXGmwPu5LnMyCHJmZb4Htq6Ws7JbVZeMH_Qi0dOlc-wcEyUAUCq44IC6fofUT1KwN66_-YJ8yEEn0QlNzWTceVcBu9QvxK7uBFHQiYKIWdD6NS4RTM-aBJWO4jEDMq35AvEB6ofYXw52_O4AmUX23l8TPYPpNumJDrA_XRG6u7AymPZREXNm5EA6N0X9MyED3lNhaX1jxEDQIrBd57C54Jd9W99xg_9WPFcJY2YPZWaXnJ7Kaoh50dxjk5kk_aH1kTXtsCa96mVXxjtLm9P1klu82TqaFgnOUfazUXoVE5kDLet4SueLPIyR1h79gWqKLyFNBqBswRAPVgzLHh3kycoSbnZ0z8j5P5XskmXg2VfLtN_F7y299ECIzLjJpx7z4kN-Iw5vo33OPHcSIO3ylQeYd_QGbBuivJz9LhQ4bBs0_mYbeJpmxdLLVL_MXM3RVrRP5sc29HSmh1-WES9zyKQN5ZnZ9DPXOZeoplawuwQL8IlbvPDgUaTjaoYFz5OF_91Xw_lynPoT9WeOnAa-gptdy0]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-deployment.puml b/design-documents/images/097/sesame-deployment.puml @@ -0,0 +1,25 @@ +@startuml sesame-deployment +' Deployment view for DD 97 (Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml + +rectangle "User side" { + rectangle "Taler Wallet\n(smartphone)" as wallet #d3deff +} + +rectangle "Operator side" { + rectangle "Merchant backend\n(Taler)" as merchant #d3deff + rectangle "Access control\nsystem (ACS)" as acs #d3deff + rectangle "remote, offline" #f1f1f4 { + rectangle "Lock\n(door hardware,\nno network)" as lock #d3deff + } +} + +rectangle "IAM\n(e.g. OIDC provider)" as iam #d3deff + +wallet -right-> merchant : HTTPS (public API) +wallet -right-> lock : NFC +acs -up-> merchant : private API +acs -right-> iam : identity +acs .down.> lock : configuration\n(setup only, out-of-band) +@enduml diff --git a/design-documents/images/097/sesame-deployment.svg b/design-documents/images/097/sesame-deployment.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="455px" preserveAspectRatio="none" style="width:672px;height:455px;background:#FFFFFF;" version="1.1" viewBox="0 0 672 455" width="672px" zoomAndPan="magnify"><defs/><g><!--cluster User side--><g id="cluster_User side"><rect fill="#FFFFFF" height="104" rx="4" ry="4" style="stroke:#586A88;stroke-width:1;" width="148" x="7" y="7"/><text fill="#00134A" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="73.623" x="44.1885" y="21.9951">User side</text></g><!--cluster Operator side--><g id="cluster_Operator side"><rect fill="#FFFFFF" height="442" rx="4" ry="4" style="stroke:#586A88;stroke-width:1;" width="231" x="248" y="7"/><text fill="#00134A" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="108.5068" x="309.2466" y="21.9951">Operator side</text></g><!--cluster remote, offline--><g id="cluster_remote, offline"><rect fill="#F1F1F4" height="120" rx="4" ry="4" style="stroke:#586A88;stroke-width:1;" width="166" x="272" y="305"/><text fill="#00134A" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacing" textLength="117.8447" x="296.0776" y="319.9951">remote, offline</text></g><!--entity wallet--><g id="elem_wallet"><rect fill="#D3DEFF" height="52.5938" rx="4" ry="4" style="stroke:#586A88;stroke-width:0.5;" width="115.4912" x="23.5" y="42"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="33.5" y="64.9951">Taler Wallet</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="95.4912" x="33.5" y="81.292">(smartphone)</text></g><!--entity merchant--><g id="elem_merchant"><rect fill="#D3DEFF" height="52.5938" rx="4" ry="4" style="stroke:#586A88;stroke-width:0.5;" width="150.0537" x="294" y="42"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="130.0537" x="304" y="64.9951">Merchant backend</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="46.3135" x="304" y="81.292">(Taler)</text></g><!--entity acs--><g id="elem_acs"><rect fill="#D3DEFF" height="52.5938" rx="4" ry="4" style="stroke:#586A88;stroke-width:0.5;" width="121.459" x="298.5" y="174"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="101.459" x="308.5" y="196.9951">Access control</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="94.2266" x="308.5" y="213.292">system (ACS)</text></g><!--entity lock--><g id="elem_lock"><rect fill="#D3DEFF" height="68.8906" rx="4" ry="4" style="stroke:#586A88;stroke-width:0.5;" width="132.6289" x="288.5" y="340"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="32.1699" x="298.5" y="362.9951">Lock</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="112.6289" x="298.5" y="379.292">(door hardware,</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.2051" x="298.5" y="395.5889">no network)</text></g><!--entity iam--><g id="elem_iam"><rect fill="#D3DEFF" height="52.5938" rx="4" ry="4" style="stroke:#586A88;stroke-width:0.5;" width="160.5674" x="505.5" y="174"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="25.7852" x="515.5" y="196.9951">IAM</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="140.5674" x="515.5" y="213.292">(e.g. OIDC provider)</text></g><!--link wallet to merchant--><g id="link_wallet_merchant"><path d="M138.59,68.5 C183.16,68.5 239.25,68.5 287.75,68.5 " fill="none" id="wallet-to-merchant" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="293.75,68.5,284.75,64.5,288.75,68.5,284.75,72.5,293.75,68.5" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="119.8374" x="156.75" y="61.5669">HTTPS (public API)</text></g><!--link wallet to lock--><g id="link_wallet_lock"><path d="M97.41,95.16 C125.39,137.91 185.43,225.29 248,289 C266,307.32 283.1758,321.669 302.5058,336.299 " fill="none" id="wallet-to-lock" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="307.29,339.92,302.5276,331.2991,303.3032,336.9025,297.6997,337.678,307.29,339.92" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="26.2793" x="193" y="205.0669">NFC</text></g><!--reverse link merchant to acs--><g id="link_merchant_acs"><path d="M366.5606,101.1624 C364.8106,123.9524 362.72,151.09 360.97,173.87 " fill="none" id="merchant-backto-acs" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="367.02,95.18,362.3427,103.8473,366.6372,100.1653,370.3192,104.4598,367.02,95.18" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="70.6621" x="365" y="140.0669">private API</text></g><!--link acs to iam--><g id="link_acs_iam"><path d="M419.53,200.5 C445.8,200.5 470.95,200.5 499.15,200.5 " fill="none" id="acs-to-iam" style="stroke:#0042B3;stroke-width:1;"/><polygon fill="#0042B3" points="505.15,200.5,496.15,196.5,500.15,200.5,496.15,204.5,505.15,200.5" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="49.6006" x="438" y="193.5669">identity</text></g><!--link acs to lock--><g id="link_acs_lock"><path d="M358.4,227.25 C357.7,257.24 356.6899,300.8716 355.9199,333.8816 " fill="none" id="acs-to-lock" style="stroke:#0042B3;stroke-width:1;stroke-dasharray:7.0,7.0;"/><polygon fill="#0042B3" points="355.78,339.88,359.9888,330.9757,355.8966,334.8814,351.991,330.7892,355.78,339.88" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="86.2329" x="396.0383" y="270.0669">configuration</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="162.3096" x="358" y="285.1997">(setup only, out-of-band)</text></g><!--SRC=[TL9DYzim4BtxLqopXqtAob1QACsX419A0zrkQ59woKMHnhOIUIIaiOqf-zyhEvljfX_e9BrvRzuypIsiq5lNrqWChS4E2XTWjO8Fxs6w2Uw8Yc6BKTOuoxDR-8Ob4WR929tXQWv7sKeGHnd0MqdSr3OVBe9ZMq8SYU9rxjDxzigGier6O6anYCYznTmCI1PI6qcbHPXyZnWW6eqJ-9a1l41s0mr-I6kHzpIDjGpiAqSucu2Cq8q0tEYt6eiYUxnIVV23QVSVvNiCgahkuI3L2KadzR7PMRY-e1VfA-v2AOmHb2CEpkufzf6nXkbYkJtJfOh_PWQi7UCTkAAmXfAhc-9DEkz6TrULNvmw9K_QfG4g6NGd0zxjYHmGSkV2wTp9fh8NhHx_o62zk4yYc9SvFAnNI_31jIcDfocDh9-vsLEM8fYoOl7nTmHp-BpRRROmzSt16WMBpNhsL_NeOmvVFospONZH-6i97qmxR50YdmiknC73798dOiFzYENQTPG_QwQO2rCswI-Des49a1iFZcoVScnOk48S9EdPBm00]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-door-access.puml b/design-documents/images/097/sesame-door-access.puml @@ -0,0 +1,35 @@ +@startuml sesame-door-access +' Offline door access for DD 97 (Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml +autonumber + +participant "Lock\n(offline)" as lock +participant "Taler Wallet" as wallet +participant "Merchant\nbackend" as merchant + +== read challenge == +lock -> wallet : NFC tap: door_id,\nmerchant URI, challenge (nonce) + +== create order == +wallet -> merchant : GET /templates/$door_id\n(skipped if cached) +merchant --> wallet : template details +wallet -> merchant : POST /templates/$door_id\n{challenge} +merchant --> wallet : {order_id, claim token} +wallet -> merchant : POST /orders/$ORDER_ID/claim +merchant --> wallet : contract terms (choices) +wallet -> wallet : select choice matching\na held token + +== pay (redeem token) == +wallet -> merchant : POST /orders/$ORDER_ID/pay\n{coins: [], tokens: [TokenUseSig],\nwallet_data: {choice_index,\ntokens_evs: [TokenEnvelope]}} +merchant -> merchant : verify token: h_issue,\nub_sig, token_sig, key validity,\nnot spent, family in choice +merchant -> merchant : compute pos_confirmation\n(sign challenge / TOTP code) +merchant -> merchant : blind-sign replacement with\nthe input token's issue key +merchant --> wallet : {sig, pos_confirmation,\ntoken_sigs} + +== present to lock == +wallet -> lock : NFC: pos_confirmation +lock -> lock : verify signature over own\nnonce (or TOTP code),\nconsume challenge +lock --> wallet : access granted +wallet -> wallet : (async) unblind token_sigs\ninto replacement token +@enduml diff --git a/design-documents/images/097/sesame-door-access.svg b/design-documents/images/097/sesame-door-access.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="1096px" preserveAspectRatio="none" style="width:789px;height:1096px;background:#FFFFFF;" version="1.1" viewBox="0 0 789 1096" width="789px" zoomAndPan="magnify"><defs/><g><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="39" x2="39" y1="52.5938" y2="1045.375"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="290.6719" x2="290.6719" y1="52.5938" y2="1045.375"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="546.165" x2="546.165" y1="52.5938" y2="1045.375"/><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="68.6123" x="5" y="5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="32.1699" x="23.2212" y="24.9951">Lock</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="54.6123" x="12" y="41.292">(offline)</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="68.6123" x="5" y="1044.375"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="32.1699" x="23.2212" y="1064.3701">Lock</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="54.6123" x="12" y="1080.667">(offline)</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="241.6719" y="21.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="248.6719" y="41.292">Taler Wallet</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="241.6719" y="1044.375"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="248.6719" y="1064.3701">Taler Wallet</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="507.165" y="5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="514.165" y="24.9951">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="517.3232" y="41.292">backend</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="507.165" y="1044.375"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="514.165" y="1064.3701">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="517.3232" y="1080.667">backend</text><rect fill="#D9E3F9" height="3" style="stroke:#D9E3F9;stroke-width:1;" width="782.3682" x="0" y="83.1602"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="83.1602" y2="83.1602"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="86.1602" y2="86.1602"/><rect fill="#D9E3F9" height="23.1328" style="stroke:#586A88;stroke-width:2;" width="127.1978" x="327.5852" y="72.5938"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="108.6719" x="333.5852" y="88.6606">read challenge</text><polygon fill="#0042B3" points="278.7437,137.9922,288.7437,141.9922,278.7437,145.9922,282.7437,141.9922" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="39.3062" x2="284.7437" y1="141.9922" y2="141.9922"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="46.3062" y="129.3599">1</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="112.2393" x="59.3516" y="121.7935">NFC tap: door_id,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="214.3921" x="59.3516" y="136.9263">merchant URI, challenge (nonce)</text><rect fill="#D9E3F9" height="3" style="stroke:#D9E3F9;stroke-width:1;" width="782.3682" x="0" y="170.5586"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="170.5586" y2="170.5586"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="173.5586" y2="173.5586"/><rect fill="#D9E3F9" height="23.1328" style="stroke:#586A88;stroke-width:2;" width="109.6655" x="336.3513" y="159.9922"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="91.1396" x="342.3513" y="176.0591">create order</text><polygon fill="#0042B3" points="535.145,225.3906,545.145,229.3906,535.145,233.3906,539.145,229.3906" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="541.145" y1="229.3906" y2="229.3906"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="297.7437" y="216.7583">2</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="160.7163" x="310.7891" y="209.1919">GET /templates/$door_id</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="124.0142" x="310.7891" y="224.3247">(skipped if cached)</text><polygon fill="#0042B3" points="301.7437,254.5234,291.7437,258.5234,301.7437,262.5234,297.7437,258.5234" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="295.7437" x2="546.145" y1="258.5234" y2="258.5234"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="307.7437" y="253.4575">3</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="106.1265" x="320.7891" y="253.4575">template details</text><polygon fill="#0042B3" points="535.145,298.7891,545.145,302.7891,535.145,306.7891,539.145,302.7891" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="541.145" y1="302.7891" y2="302.7891"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="297.7437" y="290.1567">4</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="168.7524" x="310.7891" y="282.5903">POST /templates/$door_id</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="79.606" x="310.7891" y="297.7231">{challenge}</text><polygon fill="#0042B3" points="301.7437,327.9219,291.7437,331.9219,301.7437,335.9219,297.7437,331.9219" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="295.7437" x2="546.145" y1="331.9219" y2="331.9219"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="307.7437" y="326.856">5</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="154.0132" x="320.7891" y="326.856">{order_id, claim token}</text><polygon fill="#0042B3" points="535.145,357.0547,545.145,361.0547,535.145,365.0547,539.145,361.0547" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="541.145" y1="361.0547" y2="361.0547"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="297.7437" y="355.9888">6</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="203.3408" x="310.7891" y="355.9888">POST /orders/$ORDER_ID/claim</text><polygon fill="#0042B3" points="301.7437,386.1875,291.7437,390.1875,301.7437,394.1875,297.7437,390.1875" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="295.7437" x2="546.145" y1="390.1875" y2="390.1875"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="307.7437" y="385.1216">7</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="159.1484" x="320.7891" y="385.1216">contract terms (choices)</text><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="332.7437" y1="434.4531" y2="434.4531"/><line style="stroke:#0042B3;stroke-width:1;" x1="332.7437" x2="332.7437" y1="434.4531" y2="447.4531"/><line style="stroke:#0042B3;stroke-width:1;" x1="291.7437" x2="332.7437" y1="447.4531" y2="447.4531"/><polygon fill="#0042B3" points="301.7437,443.4531,291.7437,447.4531,301.7437,451.4531,297.7437,447.4531" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="297.7437" y="421.8208">8</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="150.2046" x="310.7891" y="414.2544">select choice matching</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="81.1484" x="310.7891" y="429.3872">a held token</text><rect fill="#D9E3F9" height="3" style="stroke:#D9E3F9;stroke-width:1;" width="782.3682" x="0" y="476.0195"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="476.0195" y2="476.0195"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="479.0195" y2="479.0195"/><rect fill="#D9E3F9" height="23.1328" style="stroke:#586A88;stroke-width:2;" width="163.5889" x="309.3896" y="465.4531"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="145.063" x="315.3896" y="481.52">pay (redeem token)</text><polygon fill="#0042B3" points="535.145,561.1172,545.145,565.1172,535.145,569.1172,539.145,565.1172" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="541.145" y1="565.1172" y2="565.1172"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="297.7437" y="537.3521">9</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="192.2515" x="310.7891" y="514.6528">POST /orders/$ORDER_ID/pay</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="219.356" x="310.7891" y="529.7856">{coins: [], tokens: [TokenUseSig],</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="180.0068" x="310.7891" y="544.9185">wallet_data: {choice_index,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="207.3716" x="310.7891" y="560.0513">tokens_evs: [TokenEnvelope]}}</text><line style="stroke:#0042B3;stroke-width:1;" x1="547.145" x2="589.145" y1="624.5156" y2="624.5156"/><line style="stroke:#0042B3;stroke-width:1;" x1="589.145" x2="589.145" y1="624.5156" y2="637.5156"/><line style="stroke:#0042B3;stroke-width:1;" x1="548.145" x2="589.145" y1="637.5156" y2="637.5156"/><polygon fill="#0042B3" points="558.145,633.5156,548.145,637.5156,558.145,641.5156,554.145,637.5156" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="554.145" y="604.3169">10</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="138.6455" x="576.2358" y="589.1841">verify token: h_issue,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="199.1323" x="576.2358" y="604.3169">ub_sig, token_sig, key validity,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="172.3833" x="576.2358" y="619.4497">not spent, family in choice</text><line style="stroke:#0042B3;stroke-width:1;" x1="547.145" x2="589.145" y1="681.7813" y2="681.7813"/><line style="stroke:#0042B3;stroke-width:1;" x1="589.145" x2="589.145" y1="681.7813" y2="694.7813"/><line style="stroke:#0042B3;stroke-width:1;" x1="548.145" x2="589.145" y1="694.7813" y2="694.7813"/><polygon fill="#0042B3" points="558.145,690.7813,548.145,694.7813,558.145,698.7813,554.145,694.7813" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="554.145" y="669.1489">11</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173.3672" x="576.2358" y="661.5825">compute pos_confirmation</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="186.2974" x="576.2358" y="676.7153">(sign challenge / TOTP code)</text><line style="stroke:#0042B3;stroke-width:1;" x1="547.145" x2="589.145" y1="739.0469" y2="739.0469"/><line style="stroke:#0042B3;stroke-width:1;" x1="589.145" x2="589.145" y1="739.0469" y2="752.0469"/><line style="stroke:#0042B3;stroke-width:1;" x1="548.145" x2="589.145" y1="752.0469" y2="752.0469"/><polygon fill="#0042B3" points="558.145,748.0469,548.145,752.0469,558.145,756.0469,554.145,752.0469" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="554.145" y="726.4146">12</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="181.6953" x="576.2358" y="718.8481">blind-sign replacement with</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="175.0811" x="576.2358" y="733.981">the input token's issue key</text><polygon fill="#0042B3" points="301.7437,792.3125,291.7437,796.3125,301.7437,800.3125,297.7437,796.3125" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="295.7437" x2="546.145" y1="796.3125" y2="796.3125"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="307.7437" y="783.6802">13</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="151.1885" x="329.8345" y="776.1138">{sig, pos_confirmation,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="76.9971" x="329.8345" y="791.2466">token_sigs}</text><rect fill="#D9E3F9" height="3" style="stroke:#D9E3F9;stroke-width:1;" width="782.3682" x="0" y="824.8789"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="824.8789" y2="824.8789"/><line style="stroke:#586A88;stroke-width:1;" x1="0" x2="782.3682" y1="827.8789" y2="827.8789"/><rect fill="#D9E3F9" height="23.1328" style="stroke:#586A88;stroke-width:2;" width="129.0195" x="326.6743" y="814.3125"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="110.4937" x="332.6743" y="830.3794">present to lock</text><polygon fill="#0042B3" points="50.3062,864.5781,40.3062,868.5781,50.3062,872.5781,46.3062,868.5781" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="44.3062" x2="289.7437" y1="868.5781" y2="868.5781"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="56.3062" y="863.5122">14</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="146.6753" x="78.397" y="863.5122">NFC: pos_confirmation</text><line style="stroke:#0042B3;stroke-width:1;" x1="39.3062" x2="81.3062" y1="927.9766" y2="927.9766"/><line style="stroke:#0042B3;stroke-width:1;" x1="81.3062" x2="81.3062" y1="927.9766" y2="940.9766"/><line style="stroke:#0042B3;stroke-width:1;" x1="40.3062" x2="81.3062" y1="940.9766" y2="940.9766"/><polygon fill="#0042B3" points="50.3062,936.9766,40.3062,940.9766,50.3062,944.9766,46.3062,940.9766" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="46.3062" y="907.7778">15</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="166.6514" x="68.397" y="892.645">verify signature over own</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="144.8535" x="68.397" y="907.7778">nonce (or TOTP code),</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="126.2104" x="68.397" y="922.9106">consume challenge</text><polygon fill="#0042B3" points="278.7437,966.1094,288.7437,970.1094,278.7437,974.1094,282.7437,970.1094" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="39.3062" x2="284.7437" y1="970.1094" y2="970.1094"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="46.3062" y="965.0435">16</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="99.0869" x="68.397" y="965.0435">access granted</text><line style="stroke:#0042B3;stroke-width:1;" x1="290.7437" x2="332.7437" y1="1014.375" y2="1014.375"/><line style="stroke:#0042B3;stroke-width:1;" x1="332.7437" x2="332.7437" y1="1014.375" y2="1027.375"/><line style="stroke:#0042B3;stroke-width:1;" x1="291.7437" x2="332.7437" y1="1027.375" y2="1027.375"/><polygon fill="#0042B3" points="301.7437,1023.375,291.7437,1027.375,301.7437,1031.375,297.7437,1027.375" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="18.0908" x="297.7437" y="1001.7427">17</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="173.3989" x="319.8345" y="994.1763">(async) unblind token_sigs</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="152.2993" x="319.8345" y="1009.3091">into replacement token</text><!--SRC=[VLHRRzCm57xFhpXCI6jGBu-8IkM53ZG9wDHrue5EaMkV9buTExATZcZgVz-natKjBBmvplakv_EnBs6ntIgf4OGn5XZdw1niQJcVmwUFCBYnvW6vXrjqhCHqd5p24dFKQ9b7U9I-cC83spCOFJ0BbMBQrwKQXu-HT_iSN0IEFemhsa_UISrLBH0yKsX7pZSAnpByORKtkYutQ9EaOjPBBYiYWullXk_MUc1Qd-a5C0UAziwhLe4FVZ6bqCUInxWyB_g1bXUqMkiDuplK8bQMtMwIp6PWaGcWJmBh764sIu8MZ3vtX325dr-_aFrg6YFBf1YkzGi5t2slXoVmWJQQOngPEL5JPCOACagy7Hyn7z5J-7QrWed7aeBqw2RlEmdgt-raLQ40kGNEU84YJOwuqQcz5pG8z4mgzxRGpUAsH-dfQF_Ge_0KUmYT0rTCbk0DXNdudr14aCfYERzQPjVpIGJsy7EZlMKqT1vjwM302oDfBjCJWMEjGnN6ios1adbUI9slDOC2bMYTnVWhri30ea3i_AQzXz1ZcHX2EaPgDuNVzyEM9gnNON7dy5RczpGDBMacc6SKLcijarhW7_hPWZBS7u5NUe_AL7X_E0typD4UhTmshU0KYamwLoEHrPlCoRnpqYvtsC2UAIcaRwX26m-kGks7i6MbL0r8tQNL9yPDMTKqFvLn6Ht5LbgALHeTXb3c-cIy9x1Qh6u8833jOzlGhHMZ2BH8myQn93VnvQ0m2YG_fDPsSEaWTXQQw9kzsEJVrbvo3H6uGtlU5bqGyYQ-5kU77NVYJPx-mtMyy5rHbtteWFdQqWsc7J2FEeHBjni6z5g-va1MYCtL9Ru6rL6Uzj6zjBcb3b6yDTa3vXhDKwXrZF3rZDrQIshtBCxuyna0]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-onboarding.puml b/design-documents/images/097/sesame-onboarding.puml @@ -0,0 +1,21 @@ +@startuml sesame-onboarding +' User onboarding for DD 97 (Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml +autonumber + +actor User as user +participant "Taler Wallet" as wallet +participant "IAM" as iam +participant "ACS" as acs +participant "Merchant\nbackend" as merchant + +user -> iam : login (e.g. OIDC) +iam --> user : ID token +user -> acs : present identity +acs -> acs : resolve roles to\naccess groups +acs -> merchant : POST /private/fountains\n{description, poll_freq, grants} +merchant --> acs : {fountain_id, fountain_secret} +acs --> wallet : taler://fountain/... URI\n(QR code / deep link) +wallet -> merchant : initial token withdrawal\n(see withdrawal diagram) +@enduml diff --git a/design-documents/images/097/sesame-onboarding.svg b/design-documents/images/097/sesame-onboarding.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="488px" preserveAspectRatio="none" style="width:565px;height:488px;background:#FFFFFF;" version="1.1" viewBox="0 0 565 488" width="565px" zoomAndPan="magnify"><defs/><g><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="23" x2="23" y1="81.2969" y2="407.8906"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="101.9102" x2="101.9102" y1="81.2969" y2="407.8906"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="180.0537" x2="180.0537" y1="81.2969" y2="407.8906"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="279.2798" x2="279.2798" y1="81.2969" y2="407.8906"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="518.5898" x2="518.5898" y1="81.2969" y2="407.8906"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="31.9102" x="5" y="77.9951">User</text><ellipse cx="23.9551" cy="13.5" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M23.9551,21.5 L23.9551,48.5 M10.9551,29.5 L36.9551,29.5 M23.9551,48.5 L10.9551,63.5 M23.9551,48.5 L36.9551,63.5 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="31.9102" x="5" y="419.8857">User</text><ellipse cx="23.9551" cy="431.6875" fill="#D3DEFF" rx="8" ry="8" style="stroke:#0042B3;stroke-width:0.5;"/><path d="M23.9551,439.6875 L23.9551,466.6875 M10.9551,447.6875 L36.9551,447.6875 M23.9551,466.6875 L10.9551,481.6875 M23.9551,466.6875 L36.9551,481.6875 " fill="none" style="stroke:#0042B3;stroke-width:0.5;"/><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="52.9102" y="50"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="59.9102" y="69.9951">Taler Wallet</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="52.9102" y="406.8906"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="59.9102" y="426.8857">Taler Wallet</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="39.7852" x="161.0537" y="50"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="25.7852" x="168.0537" y="69.9951">IAM</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="39.7852" x="161.0537" y="406.8906"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="25.7852" x="168.0537" y="426.8857">IAM</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="42.2393" x="258.2798" y="50"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="28.2393" x="265.2798" y="69.9951">ACS</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="42.2393" x="258.2798" y="406.8906"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="28.2393" x="265.2798" y="426.8857">ACS</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="479.5898" y="33.7031"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="486.5898" y="53.6982">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="489.748" y="69.9951">backend</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="479.5898" y="406.8906"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="486.5898" y="426.8857">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="489.748" y="443.1826">backend</text><polygon fill="#0042B3" points="168.9463,108.4297,178.9463,112.4297,168.9463,116.4297,172.9463,112.4297" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="23.9551" x2="174.9463" y1="112.4297" y2="112.4297"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="30.9551" y="107.3638">1</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="107.7451" x="44.0005" y="107.3638">login (e.g. OIDC)</text><polygon fill="#0042B3" points="34.9551,137.5625,24.9551,141.5625,34.9551,145.5625,30.9551,141.5625" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="28.9551" x2="179.9463" y1="141.5625" y2="141.5625"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="40.9551" y="136.4966">2</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="54.793" x="54.0005" y="136.4966">ID token</text><polygon fill="#0042B3" points="267.3994,166.6953,277.3994,170.6953,267.3994,174.6953,271.3994,170.6953" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="23.9551" x2="273.3994" y1="170.6953" y2="170.6953"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="30.9551" y="165.6294">3</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="103.4351" x="44.0005" y="165.6294">present identity</text><line style="stroke:#0042B3;stroke-width:1;" x1="279.3994" x2="321.3994" y1="214.9609" y2="214.9609"/><line style="stroke:#0042B3;stroke-width:1;" x1="321.3994" x2="321.3994" y1="214.9609" y2="227.9609"/><line style="stroke:#0042B3;stroke-width:1;" x1="280.3994" x2="321.3994" y1="227.9609" y2="227.9609"/><polygon fill="#0042B3" points="290.3994,223.9609,280.3994,227.9609,290.3994,231.9609,286.3994,227.9609" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="286.3994" y="202.3286">4</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="100.3691" x="299.4448" y="194.7622">resolve roles to</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="92.752" x="299.4448" y="209.895">access groups</text><polygon fill="#0042B3" points="507.5698,268.2266,517.5698,272.2266,507.5698,276.2266,511.5698,272.2266" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="279.3994" x2="513.5698" y1="272.2266" y2="272.2266"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="286.3994" y="259.5942">5</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="153.8164" x="299.4448" y="252.0278">POST /private/fountains</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="203.125" x="299.4448" y="267.1606">{description, poll_freq, grants}</text><polygon fill="#0042B3" points="290.3994,297.3594,280.3994,301.3594,290.3994,305.3594,286.3994,301.3594" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="284.3994" x2="518.5698" y1="301.3594" y2="301.3594"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="296.3994" y="296.2935">6</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="197.8755" x="309.4448" y="296.2935">{fountain_id, fountain_secret}</text><polygon fill="#0042B3" points="112.9819,341.625,102.9819,345.625,112.9819,349.625,108.9819,345.625" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="106.9819" x2="278.3994" y1="345.625" y2="345.625"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="118.9819" y="332.9927">7</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="140.3721" x="132.0273" y="325.4263">taler://fountain/... URI</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="137.1602" x="132.0273" y="340.5591">(QR code / deep link)</text><polygon fill="#0042B3" points="507.5698,385.8906,517.5698,389.8906,507.5698,393.8906,511.5698,389.8906" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="101.9819" x2="513.5698" y1="389.8906" y2="389.8906"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="108.9819" y="377.2583">8</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="152.1851" x="122.0273" y="369.6919">initial token withdrawal</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="166.5879" x="122.0273" y="384.8247">(see withdrawal diagram)</text><!--SRC=[LLBDJkCm4BxxAMQvqAB6EQwsXvKGlVI0O5iGbqfeQa_35CSsjbE44E_E9ANHzXBRtz-CFRc4nqm9Wjy6J9Pz0xkGOB60FxzXSf_2dao1DMLiQQhL9Qoe8Ky92y4xbvSvxF60KEqnGNJeIzSwtH-gaWyDvC5ONUaekFh5thZE4XHqbAfSFXnfxXdiIl1TkwMa59eY7GnTOOPELXKn5JOS9Hyk7depFA5pL2vwoVkmFHSjhsy7Zh4z9wvlrWE19fyJjvJCY-msVelcbRmTPEqFgbJV25H_-qIOWmiDUvYGRZJSBHStKzNZbV23RWxB1PGWCQDF2WeS4sMIScpbo-L3zV382XdSWI05Hrdy6u_6KCxGfD35V1AVcXB7_Ttw0UgO-20JgNUXymNPvut_j9HDubWu-1d4uDppBj7RJAB4cR_K65ADvJzF_cUsCnWFcKoYydMiBkBZUuj-6EEy7ilMMcjuN2qtVl9l1IR8f6kmH14S-zUf-Z6Utu0z5qPtVAtXdx89HIaXcUW_02oZDDzElm40]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/sesame-withdraw.puml b/design-documents/images/097/sesame-withdraw.puml @@ -0,0 +1,18 @@ +@startuml sesame-withdraw +' Token withdrawal via fountain for DD 97 (Project Sesame). +' Regenerate with: java -jar plantuml.jar -tsvg sesame-*.puml +!include taler-style.iuml +autonumber + +participant "Taler Wallet" as wallet +participant "Merchant\nbackend" as merchant + +wallet -> merchant : GET /fountain/info\nAuthorization: Bearer $FOUNTAIN_SECRET +merchant --> wallet : {poll_freq, grants with\ntoken family issue keys} +wallet -> wallet : prepare blinded envelopes\nfor granted families +wallet -> merchant : POST /fountain/withdraw\n{fountain_secret, grants: [{token_family_slug,\nvalid_at, envelopes}]} +merchant -> merchant : per entry: check grant and\nper-period limit for the slot +merchant -> merchant : blind-sign the envelopes\nwith the slot's issue key +merchant --> wallet : {grants: [{token_family_slug,\nh_issue, blind_sigs}]} +wallet -> wallet : unblind and store\none token per group +@enduml diff --git a/design-documents/images/097/sesame-withdraw.svg b/design-documents/images/097/sesame-withdraw.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="545px" preserveAspectRatio="none" style="width:595px;height:545px;background:#FFFFFF;" version="1.1" viewBox="0 0 595 545" width="595px" zoomAndPan="magnify"><defs/><g><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="54" x2="54" y1="52.5938" y2="493.8516"/><line style="stroke:#586A88;stroke-width:0.5;stroke-dasharray:5.0,5.0;" x1="391.9302" x2="391.9302" y1="52.5938" y2="493.8516"/><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="5" y="21.2969"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="12" y="41.292">Taler Wallet</text><rect fill="#D3DEFF" height="30.2969" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="98.1436" x="5" y="492.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84.1436" x="12" y="512.8467">Taler Wallet</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="352.9302" y="5"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="359.9302" y="24.9951">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="363.0884" y="41.292">backend</text><rect fill="#D3DEFF" height="46.5938" rx="2.5" ry="2.5" style="stroke:#0042B3;stroke-width:0.5;" width="79.96" x="352.9302" y="492.8516"/><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="65.96" x="359.9302" y="512.8467">Merchant</text><text fill="#00134A" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="59.6436" x="363.0884" y="529.1436">backend</text><polygon fill="#0042B3" points="380.9102,94.8594,390.9102,98.8594,380.9102,102.8594,384.9102,98.8594" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="54.0718" x2="386.9102" y1="98.8594" y2="98.8594"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="61.0718" y="86.2271">1</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="117.4253" x="74.1172" y="78.6606">GET /fountain/info</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="276.1294" x="74.1172" y="93.7935">Authorization: Bearer $FOUNTAIN_SECRET</text><polygon fill="#0042B3" points="65.0718,139.125,55.0718,143.125,65.0718,147.125,61.0718,143.125" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="59.0718" x2="391.9102" y1="143.125" y2="143.125"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="71.0718" y="130.4927">2</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="146.0215" x="84.1172" y="122.9263">{poll_freq, grants with</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="160.9956" x="84.1172" y="138.0591">token family issue keys}</text><line style="stroke:#0042B3;stroke-width:1;" x1="54.0718" x2="96.0718" y1="187.3906" y2="187.3906"/><line style="stroke:#0042B3;stroke-width:1;" x1="96.0718" x2="96.0718" y1="187.3906" y2="200.3906"/><line style="stroke:#0042B3;stroke-width:1;" x1="55.0718" x2="96.0718" y1="200.3906" y2="200.3906"/><polygon fill="#0042B3" points="65.0718,196.3906,55.0718,200.3906,65.0718,204.3906,61.0718,200.3906" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="61.0718" y="174.7583">3</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="174.1543" x="74.1172" y="167.1919">prepare blinded envelopes</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="128.1021" x="74.1172" y="182.3247">for granted families</text><polygon fill="#0042B3" points="380.9102,255.7891,390.9102,259.7891,380.9102,263.7891,384.9102,259.7891" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;" x1="54.0718" x2="386.9102" y1="259.7891" y2="259.7891"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="61.0718" y="239.5903">4</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="160.856" x="74.1172" y="224.4575">POST /fountain/withdraw</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="301.793" x="74.1172" y="239.5903">{fountain_secret, grants: [{token_family_slug,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="147.0942" x="74.1172" y="254.7231">valid_at, envelopes}]}</text><line style="stroke:#0042B3;stroke-width:1;" x1="392.9102" x2="434.9102" y1="304.0547" y2="304.0547"/><line style="stroke:#0042B3;stroke-width:1;" x1="434.9102" x2="434.9102" y1="304.0547" y2="317.0547"/><line style="stroke:#0042B3;stroke-width:1;" x1="393.9102" x2="434.9102" y1="317.0547" y2="317.0547"/><polygon fill="#0042B3" points="403.9102,313.0547,393.9102,317.0547,403.9102,321.0547,399.9102,317.0547" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="399.9102" y="291.4224">5</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="174.2939" x="412.9556" y="283.856">per entry: check grant and</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="175.4683" x="412.9556" y="298.9888">per-period limit for the slot</text><line style="stroke:#0042B3;stroke-width:1;" x1="392.9102" x2="434.9102" y1="361.3203" y2="361.3203"/><line style="stroke:#0042B3;stroke-width:1;" x1="434.9102" x2="434.9102" y1="361.3203" y2="374.3203"/><line style="stroke:#0042B3;stroke-width:1;" x1="393.9102" x2="434.9102" y1="374.3203" y2="374.3203"/><polygon fill="#0042B3" points="403.9102,370.3203,393.9102,374.3203,403.9102,378.3203,399.9102,374.3203" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="399.9102" y="348.688">6</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="159.6499" x="412.9556" y="341.1216">blind-sign the envelopes</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="155.8413" x="412.9556" y="356.2544">with the slot's issue key</text><polygon fill="#0042B3" points="65.0718,414.5859,55.0718,418.5859,65.0718,422.5859,61.0718,418.5859" style="stroke:#0042B3;stroke-width:1;"/><line style="stroke:#0042B3;stroke-width:1;stroke-dasharray:2.0,2.0;" x1="59.0718" x2="391.9102" y1="418.5859" y2="418.5859"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="71.0718" y="405.9536">7</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="192.7466" x="84.1172" y="398.3872">{grants: [{token_family_slug,</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="141.8892" x="84.1172" y="413.52">h_issue, blind_sigs}]}</text><line style="stroke:#0042B3;stroke-width:1;" x1="54.0718" x2="96.0718" y1="462.8516" y2="462.8516"/><line style="stroke:#0042B3;stroke-width:1;" x1="96.0718" x2="96.0718" y1="462.8516" y2="475.8516"/><line style="stroke:#0042B3;stroke-width:1;" x1="55.0718" x2="96.0718" y1="475.8516" y2="475.8516"/><polygon fill="#0042B3" points="65.0718,471.8516,55.0718,475.8516,65.0718,479.8516,61.0718,475.8516" style="stroke:#0042B3;stroke-width:1;"/><text fill="#00134A" font-family="sans-serif" font-size="13" font-weight="bold" lengthAdjust="spacing" textLength="9.0454" x="61.0718" y="450.2192">8</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="114.334" x="74.1172" y="442.6528">unblind and store</text><text fill="#00134A" font-family="sans-serif" font-size="13" lengthAdjust="spacing" textLength="133.0405" x="74.1172" y="457.7856">one token per group</text><!--SRC=[VLB1RjD04BtxAuOAAO3Yz8Zm0QdGW3ZGLeqH1uoiYJsnDrdFcjsnen3btxlUD2QLMWwMnZDltinxinD8pOOOjahgqk8MDVGAOMKw5bJi0mlNr_3XFRovisPDXS2273RqTXPDu9ugOh8e50WIM6EF4A_HGgkHfMlqRFY9nVKLkD0OlvkrFX-zKbpehYGGr6HZ9pjDCpLKi1F3NRCa6qKjMb65QZqPNAG34dwYrYGNW0ws8NmA-awsg7sKyH8BBwqCoEOn6qN77eW_ZZb8uEiyXSkJw4l5AvFnLIUriUelYZASm2T2wwU__dBxuoQz-dQJB-QVx-TfDBB4dlAHF85zQxJELvR-JA6olko2GnbBi7k5ZT8xKCvr11lQkSFPNYD9QybB8rXgnIMLGDoJDYsvZ8Ux15wV3cIAtFFIxcuNvzfEXyvuVyhbZWfBSbeqWL_xi6L-t39tkgkc6VUeLPcZnurx77uVplG_6TjwhuZ5xX8eQYesHt90BZFsjTX_ofIWLQCal3Af2PmsyX9XC25sgk80FVDYa3IsJzm_Lr-wpVzrrdaWc1udvdvYqFdCVJeEa44KE36MCZRi7tGuyM10PKtNFW00]--></g></svg> +\ No newline at end of file diff --git a/design-documents/images/097/taler-style.iuml b/design-documents/images/097/taler-style.iuml @@ -0,0 +1,78 @@ +' Shared PlantUML style for the DD 97 diagrams, using the Taler +' brand palette (see DD 90 "Branding" and DD 66 "Wallet color scheme"): +' primary #0042b3 +' primaryContainer #d3deff +' onPrimaryContainer #00134a +' secondary #586a88 +' secondaryContainer #d9e3f9 +' surface #f1f1f4 +' Included by the sesame-*.puml sources; not a standalone diagram. + +skinparam shadowing false +skinparam defaultFontName sans-serif +skinparam ArrowColor #0042b3 +skinparam ArrowFontColor #00134a + +skinparam participant { + BackgroundColor #d3deff + BorderColor #0042b3 + FontColor #00134a +} + +skinparam actor { + BackgroundColor #d3deff + BorderColor #0042b3 + FontColor #00134a +} + +skinparam sequence { + LifeLineBorderColor #586a88 + GroupBackgroundColor #d9e3f9 + GroupBorderColor #586a88 + GroupFontColor #00134a + GroupHeaderFontColor #00134a + DividerBackgroundColor #d9e3f9 + DividerBorderColor #586a88 + DividerFontColor #00134a +} + +skinparam note { + BackgroundColor #f1f1f4 + BorderColor #586a88 + FontColor #00134a +} + +' Plain rectangles act as containers (deployment view); leaf nodes +' set #d3deff explicitly in the diagram source. +skinparam rectangle { + RoundCorner 8 + BackgroundColor #ffffff + BorderColor #586a88 + FontColor #00134a +} + +skinparam package { + BackgroundColor #ffffff + BorderColor #586a88 + FontColor #00134a +} + +skinparam class { + HeaderBackgroundColor #d3deff + BackgroundColor #ffffff + BorderColor #0042b3 + FontColor #00134a + AttributeFontColor #00134a +} + +skinparam activity { + BackgroundColor #d3deff + BorderColor #0042b3 + FontColor #00134a + StartColor #0042b3 + EndColor #0042b3 + BarColor #586a88 + DiamondBackgroundColor #d9e3f9 + DiamondBorderColor #586a88 + DiamondFontColor #00134a +} diff --git a/design-documents/index.rst b/design-documents/index.rst @@ -108,4 +108,5 @@ Design documents that start with "XX" are considered deprecated. 094-discounts-passes-wallet 095-captcha-100 096-partial-payments + 097-project-sesame 999-template