summaryrefslogtreecommitdiff
path: root/doc/sphinx/reducer.rst
diff options
context:
space:
mode:
Diffstat (limited to 'doc/sphinx/reducer.rst')
-rw-r--r--doc/sphinx/reducer.rst1656
1 files changed, 1656 insertions, 0 deletions
diff --git a/doc/sphinx/reducer.rst b/doc/sphinx/reducer.rst
new file mode 100644
index 0000000..e5f1699
--- /dev/null
+++ b/doc/sphinx/reducer.rst
@@ -0,0 +1,1656 @@
+..
+ This file is part of Anastasis
+ Copyright (C) 2019-2021 Anastasis SARL
+
+ Anastasis is free software; you can redistribute it and/or modify it under the
+ terms of the GNU Affero General Public License as published by the Free Software
+ Foundation; either version 2.1, or (at your option) any later version.
+
+ Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+
+ @author Christian Grothoff
+ @author Dominik Meister
+ @author Dennis Neufeld
+
+-----------
+Reducer API
+-----------
+
+This section describes the Anastasis Reducer API which is used by client applications
+to store or load the different states the client application can have.
+The reducer takes a state_ in JSON syntax and returns the new state in JSON syntax.
+
+For example a **state** may take the following structure:
+
+.. code-block:: json
+
+ {
+ "backup_state": "CONTINENT_SELECTING",
+ "continents": [
+ "Europe",
+ "North_America"
+ ]
+ }
+
+The new state depends on the previous one and on the transition action_ with its
+arguments given to the reducer. A **transition argument** also is a statement in JSON syntax:
+
+.. code-block:: json
+
+ {
+ "continent": "Europe"
+ }
+
+The new state returned by the reducer with the state and transition argument defined
+above would look like following for the transition action_ ``select_continent``:
+
+.. code-block:: json
+
+ {
+ "backup_state": "COUNTRY_SELECTING",
+ "continents": [
+ "Europe",
+ "North_America"
+ ],
+ "selected_continent": "Europe",
+ "countries": [
+ {
+ "code": "ch",
+ "name": "Switzerland",
+ "continent": "Europe",
+ "name_i18n": {
+ "de_DE": "Schweiz",
+ "de_CH": "Schwiiz",
+ "fr": "Suisse",
+ "en": "Swiss"
+ },
+ "currency": "CHF"
+ },
+ {
+ "code": "de",
+ "name": "Germany",
+ "continent": "Europe",
+ "continent_i18n": {
+ "de": "Europa"
+ },
+ "name_i18n": {
+ "de_DE": "Deutschland",
+ "de_CH": "Deutschland",
+ "fr": "Allemagne",
+ "en": "Germany"
+ },
+ "currency": "EUR"
+ }
+ ]
+ }
+
+States
+^^^^^^
+
+Overall, the reducer knows the following states:
+
+ - **ERROR**: The transition led to an error. No further transitions are possible from
+ this state, but the client may want to continue from a previous state.
+ - **CONTINENT_SELECTING**: The user should specify the continent where they are living,
+ so that we can show a list of countries to choose from.
+ - **COUNTRY_SELECTING**: The user should specify the country where they are living,
+ so that we can determine appropriate attributes, currencies and Anastasis
+ providers.
+ - **USER_ATTRIBUTES_COLLECTING**: The user should provide the country-specific personal
+ attributes.
+ - **AUTHENTICATIONS_EDITING**: The user should add authentication methods to be used
+ during recovery.
+ - **POLICIES_REVIEWING**: The user should review the recovery policies.
+ - **SECRET_EDITING**: The user should edit the secret to be backed up.
+ - **TRUTHS_PAYING**: The user needs to pay for one or more uploads of data associated
+ with an authentication method.
+ - **POLICIES_PAYING**: The user needs to pay for storing the recovery policy document.
+ - **BACKUP_FINISHED**: A backup has been successfully generated.
+ - **SECRET_SELECTING**: The user needs to select a recovery policy document with
+ the secret that is to be recovered.
+ - **CHALLENGE_SELECTING**: The user needs to select an authorization challenge to
+ proceed with recovery.
+ - **CHALLENGE_PAYING**: The user needs to pay to proceed with the authorization challenge.
+ - **CHALLENGE_SOLVING**: The user needs to solve the authorization challenge.
+ - **RECOVERY_FINISHED**: The secret of the user has been recovered.
+
+State names:
+
+ - In SELECTING-states, the user has to choose one value out of a predefined set of values (for example a continent out of a set of continents).
+ - In COLLECTING-states, the user has to give certain values.
+ - In EDITING-states, the user is free to choose which values he wants to give.
+ - In REVEIWING-states, the user may make a few choices, but primarily is expected to affirm something.
+ - in PAYING-states, the user must make a payment.
+ - in FINISHED-states, the operation has definitively concluded.
+
+
+Backup Reducer
+^^^^^^^^^^^^^^
+.. _state:
+.. _action:
+.. figure:: anastasis_reducer_backup.png
+ :name: fig-anastasis_reducer_backup
+ :alt: fig-anastasis_reducer_backup
+ :scale: 75 %
+ :align: center
+
+ Backup states and their transitions.
+
+
+The illustration above shows the different states the reducer can have during a backup
+process.
+
+
+Recovery Reducer
+^^^^^^^^^^^^^^^^
+.. figure:: anastasis_reducer_recovery.png
+ :name: fig-anastasis_reducer_recovery
+ :alt: fig-anastasis_reducer_recovery
+ :scale: 75 %
+ :align: center
+
+ Recovery states and their transitions.
+
+
+The illustration above shows the different states the reducer can have during a recovery
+process.
+
+
+Reducer transitions
+^^^^^^^^^^^^^^^^^^^
+In the following, the individual transitions will be specified in more detail.
+Note that we only show fields added by the reducer, typically the previous
+state is preserved to enable "back" transitions to function smoothly.
+
+
+Initial state
+-------------
+
+The initial states for backup and recovery processes are:
+
+**Initial backup state:**
+
+.. code-block:: json
+
+ {
+ "backup_state": "CONTINENT_SELECTING",
+ "continents": [
+ "Europe",
+ "North America"
+ ]
+ }
+
+
+**Initial recovery state:**
+
+.. code-block:: json
+
+ {
+ "recovery_state": "CONTINENT_SELECTING",
+ "continents": [
+ "Europe",
+ "North America"
+ ]
+ }
+
+Here, "continents" is an array of English strings with the names of the
+continents which contain countries for which Anastasis could function (based
+on having providers that are known to operate and rules being provided for
+user attributes from those countries).
+
+For internationalization, another field ``continents_i18n`` may be present.
+This field would be a map of language names to arrays of translated
+continent names:
+
+.. code-block:: json
+
+ {
+ "recovery_state": "CONTINENT_SELECTING",
+ "continents": [
+ "Europe",
+ "North America"
+ ]
+ "continents_i18n":
+ {
+ "de_DE" : [
+ "Europa",
+ "Nordamerika"
+ ],
+ "de_CH" : [
+ "Europa",
+ "Nordamerika"
+ ]
+ }
+ }
+
+Translations must be given in the same order as the main English array.
+
+
+Common transitions
+------------------
+
+**select_continent:**
+
+Here the user specifies the continent they live on. Arguments (example):
+
+.. code-block:: json
+
+ {
+ "continent": "Europe"
+ }
+
+The continent must be given using the English name from the ``continents`` array.
+Using a translated continent name is invalid and may result in failure.
+
+The reducer returns an updated state with a list of countries to choose from,
+for example:
+
+.. code-block:: json
+
+ {
+ "backup_state": "COUNTRY_SELECTING",
+ "selected_continent": "Europe",
+ "countries": [
+ {
+ "code": "ch",
+ "name": "Switzerland",
+ "continent": "Europe",
+ "name_i18n": {
+ "de_DE": "Schweiz",
+ "de_CH": "Schwiiz",
+ "fr": "Suisse",
+ "en": "Swiss"
+ },
+ "currency": "CHF"
+ },
+ {
+ "code": "de",
+ "name": "Germany",
+ "continent": "Europe",
+ "continent_i18n": {
+ "de": "Europa"
+ },
+ "name_i18n": {
+ "de_DE": "Deutschland",
+ "de_CH": "Deutschland",
+ "fr": "Allemagne",
+ "en": "Germany"
+ },
+ "currency": "EUR"
+ }
+ ]
+ }
+
+Here ``countries`` is an array of countries on the ``selected_continent``. For
+each country, the ``code`` is the ISO 3166-1 alpha-2 country code. The
+``continent`` is only present because some countries span continents, the
+information is redundant and will always match ``selected_continent``. The
+``name`` is the name of the country in English, internationalizations of the
+name may be provided in ``name_i18n``. The ``currency`` is **an** official
+currency of the country, if a country has multiple currencies, it may appear
+multiple times in the list. In this case, the user should select the entry
+with the currency they intend to pay with. It is also possible for users
+to select a currency that does not match their country, but user interfaces
+should by default try to use currencies that match the user's residence.
+
+
+**select_country:**
+
+Selects the country (via the country code) and specifies the currency.
+The latter is needed as some countries have more than one currency,
+and some use-cases may also involve users insisting on paying with
+foreign currency.
+
+Arguments (example):
+
+.. code-block:: json
+
+ {
+ "country_code": "de",
+ "currency": "EUR"
+ }
+
+The ``country_code`` must be an ISO 3166-1 alpha-2 country code from
+the array of ``countries`` of the reducer's state. The ``currency``
+field must be a valid currency accepted by the Taler payment system.
+
+The reducer returns a new state with the list of attributes the
+user is expected to provide, as well as possible authentication
+providers that accept payments in the selected currency:
+
+.. code-block:: json
+
+ {
+ "backup_state": "USER_ATTRIBUTES_COLLECTING",
+ "selected_country": "de",
+ "currency": "EUR",
+ "required_attributes": [
+ {
+ "type": "string",
+ "name": "full_name",
+ "label": "Full name",
+ "label_i18n": {
+ "de_DE": "Vollstaendiger Name",
+ "de_CH": "Vollstaendiger. Name",
+ "fr": "Nom complet",
+ "en": "Full name"
+ },
+ "widget": "anastasis_gtk_ia_full_name",
+ "uuid" : "9e8f463f-575f-42cb-85f3-759559997331"
+ },
+ {
+ "type": "date",
+ "name": "birthdate",
+ "label": "Birthdate",
+ "label_i18n": {
+ "de_DE": "Geburtsdatum",
+ "de_CH": "Geburtsdatum",
+ "fr": "Date de naissance",
+ "en": "Birthdate"
+ },
+ "uuid" : "83d655c7-bdb6-484d-904e-80c1058c8854"
+ "widget": "anastasis_gtk_ia_birthdate"
+ },
+ {
+ "type": "string",
+ "name": "tax_number",
+ "label": "Taxpayer identification number",
+ "label_i18n":{
+ "de_DE": "Steuerliche Identifikationsnummer",
+ "de_CH": "Steuerliche Identifikationsnummer",
+ "en": "German taxpayer identification number"
+ },
+ "widget": "anastasis_gtk_ia_tax_de",
+ "uuid": "dae48f85-e3ff-47a4-a4a3-ed981ed8c3c6",
+ "validation-regex": "^[0-9]{11}$",
+ "validation-logic": "DE_TIN_check"
+ },
+ {
+ "type": "string",
+ "name": "social_security_number",
+ "label": "Social security number",
+ "label_i18n": {
+ "de_DE": "Sozialversicherungsnummer",
+ "de_CH": "Sozialversicherungsnummer",
+ "fr": "Numéro de sécurité sociale",
+ "en": "Social security number"
+ },
+ "widget": "anastasis_gtk_ia_ssn",
+ "validation-regex": "^[0-9]{8}[[:upper:]][0-9]{3}$",
+ "validation-logic": "DE_SVN_check"
+ "optional" : true
+ }
+ ],
+ "authentication_providers": {
+ "http://localhost:8089/": {
+ "http_status": 200,
+ "methods": [
+ { "type" : "question",
+ "usage_fee" : "EUR:0.0" },
+ { "type" : "sms",
+ "usage_fee" : "EUR:0.5" }
+ ],
+ "annual_fee": "EUR:4.99",
+ "truth_upload_fee": "EUR:4.99",
+ "liability_limit": "EUR:1",
+ "currency": "EUR",
+ "truth_lifetime": { "d_ms" : 50000000 },
+ "storage_limit_in_megabytes": 1,
+ "provider_name": "Anastasis 4",
+ "salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
+ },
+ "http://localhost:8088/": {
+ "http_status": 200,
+ "methods": [
+ { "type" : "question",
+ "usage_fee" : "EUR:0.01" },
+ { "type" : "sms",
+ "usage_fee" : "EUR:0.55" }
+ ],
+ "annual_fee": "EUR:0.99",
+ "truth_upload_fee": "EUR:3.99",
+ "liability_limit": "EUR:1",
+ "currency": "EUR",
+ "truth_lifetime": { "d_ms" : 50000000 },
+ "storage_limit_in_megabytes": 1,
+ "provider_name": "Anastasis 4",
+ "salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
+ }
+ }
+ }
+
+The array of ``required_attributes`` contains attributes about the user
+that must be provided includes:
+
+ - **type**: The type of the attribute, for now only ``string`` and ``date`` are
+ supported.
+ - **name**: The name of the attribute, this is the key under which the
+ attribute value must be provided later. The name must be unique per response.
+ - **label**: A human-readable description of the attribute in English.
+ Translated descriptions may be provided under **label_i18n**.
+ - **uuid**: A UUID that uniquely identifies identical attributes across
+ different countries. Useful to preserve values should the user enter
+ some attributes, and then switch to another country. Note that
+ attributes must not be preserved if they merely have the same **name**,
+ only the **uuid** will be identical if the semantics is identical.
+ - **widget**: An optional name of a widget that is known to nicely render
+ the attribute entry in user interfaces where named widgets are
+ supported.
+ - **validation-regex**: An optional extended POSIX regular expression
+ that is to be used to validate (string) inputs to ensure they are
+ well-formed.
+ - **validation-logic**: Optional name of a function that should be called
+ to validate the input. If the function is not known to the particular
+ client, the respective validation can be skipped (at the expense of
+ typos by users not being detected, possibly rendering secrets
+ irrecoverable).
+ - **optional**: Optional boolean field that, if ``true``, indicates that
+ this attribute is not actually required but optional and users MAY leave
+ it blank in case they do not have the requested information. Used for
+ common fields that apply to some large part of the population but are
+ not sufficiently universal to be actually required.
+
+The authentication providers are listed under a key that is the
+base URL of the service. For each provider, the following
+information is provided if the provider was successfully contacted:
+
+ - **http_status**: HTTP status code, always ``200`` on success.
+ - **methods**: Array of authentication methods supported by this
+ provider. Includes the **type** of the authentication method
+ and the **usage_fee** (how much the user must pay for authorization
+ using this method during recovery).
+ - **annual_fee**: Fee the provider charges to store the recovery
+ policy for one year.
+ - **truth_upload_fee**: Fee the provider charges to store a key share.
+ - **liability_limit**: Amount the provider can be held liable for in
+ case a key share or recovery document cannot be recovered due to
+ provider failures.
+ - **currency**: Currency in which the provider wants to be paid,
+ will match all of the fees.
+ - **storage_limit_in_megabytes**: Maximum size of an upload (for
+ both recovery document and truth data) in megabytes.
+ - **provider_name**: Human-readable name of the provider's business.
+ - **salt**: Salt value used by the provider, used to derive the
+ user's identity at this provider. Should be unique per provider,
+ and must never change for a given provider. The salt is
+ base32 encoded.
+
+If contacting the provider failed, the information returned is:
+
+ - **http_status**: HTTP status code (if available, possibly 0 if
+ we did not even obtain an HTTP response).
+ - **error_code**: Taler error code, never 0.
+
+
+**add_provider**:
+
+This operation can be performed in state ``USER_ATTRIBUTES_COLLECTING``. It
+adds one or more Anastasis providers to the list of providers the reducer
+should henceforth consider. Note that removing providers is not possible at
+this time.
+
+Here, the client must provide an array with the base URLs of the
+providers to add, for example:
+
+.. code-block:: json
+
+ {
+ "urls": [
+ "http://localhost:8888/",
+ "http://localhost:8089/"
+ ]
+ }
+
+Note that existing providers will remain in the state. The following is an
+example for an expected new state where the service on port 8089 is
+unreachable, the service on port 8088 was previously known, and service on
+port 8888 was now added:
+
+.. code-block:: json
+
+ {
+ "backup_state": "USER_ATTRIBUTES_COLLECTING",
+ "authentication_providers": {
+ "http://localhost:8089/": {
+ "error_code": 11,
+ "http_status": 0
+ },
+ "http://localhost:8088/": {
+ "http_status": 200,
+ "methods": [
+ { "type" : "question",
+ "usage_fee" : "EUR:0.01" },
+ { "type" : "sms",
+ "usage_fee" : "EUR:0.55" }
+ ],
+ "annual_fee": "EUR:0.99",
+ "truth_upload_fee": "EUR:3.99",
+ "liability_limit": "EUR:1",
+ "currency": "EUR",
+ "truth_lifetime": { "d_ms" : 50000000 },
+ "storage_limit_in_megabytes": 1,
+ "provider_name": "Anastasis 4",
+ "salt": "CXAPCKSH9D3MYJTS9536RHJHCW"
+ }
+ "http://localhost:8888/": {
+ "methods": [
+ { "type" : "question",
+ "usage_fee" : "EUR:0.01" },
+ { "type" : "sms",
+ "usage_fee" : "EUR:0.55" }
+ ],
+ "annual_fee": "EUR:0.99",
+ "truth_upload_fee": "EUR:3.99",
+ "liability_limit": "EUR:1",
+ "currency": "EUR",
+ "truth_lifetime": { "d_ms" : 50000000 },
+ "storage_limit_in_megabytes": 1,
+ "provider_name": "Anastasis 42",
+ "salt": "BXAPCKSH9D3MYJTS9536RHJHCX"
+ }
+ }
+ }
+
+
+
+Backup transitions
+------------------
+
+**enter_user_attributes:**
+
+This transition provides the user's personal attributes. The specific set of
+attributes required depends on the country of residence of the user. Some
+attributes may be optional, in which case they should be omitted entirely
+(that is, not simply be set to ``null`` or an empty string). Example
+arguments would be:
+
+.. code-block:: json
+
+ {
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "social_security_number": "123456789",
+ "birthdate": "2000-01-01",
+ "birthplace": "Earth"
+ }
+ }
+
+Note that at this stage, the state machines between backup and
+recovery diverge and the ``recovery_state`` will begin to look
+very different from the ``backup_state``.
+
+For backups, if all required attributes are present, the reducer will
+transition to an ``AUTHENTICATIONS_EDITING`` state with the attributes added
+to it:
+
+.. code-block:: json
+
+ {
+ "backup_state": "AUTHENTICATIONS_EDITING",
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "social_security_number": "123456789",
+ "birthdate": "2000-01-01",
+ "birthplace": "Earth"
+ }
+ }
+
+If required attributes are missing, do not match the required regular
+expression, or fail the custom validation logic, the reducer SHOULD transition
+to an error state indicating what was wrong about the input. A reducer that
+does not support some specific validation logic MAY accept the invalid input
+and proceed anyway. The error state will include a Taler error code that
+is specific to the failure, and optional details. Example:
+
+.. code-block:: json
+
+ {
+ "backup_state": "ERROR",
+ "code": 8404,
+ "hint": "An input did not match the regular expression.",
+ "detail": "social_security_number"
+ }
+
+Clients may safely repeat this transition to validate the user's inputs
+until they satisfy all of the constraints. This way, the user interface
+does not have to perform the input validation directly.
+
+
+**add_authentication**:
+
+This transition adds an authentication method. The method must be supported
+by one or more providers that are included in the current state. Adding an
+authentication method requires specifying the ``type`` and ``instructions`` to
+be given to the user. The ``challenge`` is encrypted and stored at the
+Anastasis provider. The specific semantics of the value depend on the
+``type``. Typical challenges values are a phone number (to send an SMS to),
+an e-mail address (to send a PIN code to) or the answer to a security
+question. Note that these challenge values will still be encrypted (and
+possibly hashed) before being given to the Anastasis providers.
+
+Note that the ``challenge`` must be given in Crockford Base32 encoding, as it
+MAY include binary data (such as a photograph of the user). In the latter
+case, the optional ``mime_type`` field must be provided to give the MIME type
+of the value encoded in ``challenge``.
+
+.. code-block:: json
+
+ {
+ "authentication_method":
+ {
+ "type": "question",
+ "mime_type" : "text/plain",
+ "instructions" : "What is your favorite GNU package?",
+ "challenge" : "E1QPPS8A",
+ }
+ }
+
+If the information provided is valid, the reducer will add the new
+authentication method to the array of authentication methods:
+
+.. code-block:: json
+
+ {
+ "backup_state": "AUTHENTICATIONS_EDITING",
+ "authentication_methods": [
+ {
+ "type": "question",
+ "mime_type" : "text/plain",
+ "instructions" : "What is your favorite GNU package?",
+ "challenge" : "E1QPPS8A",
+ },
+ {
+ "type": "email",
+ "instructions" : "E-mail to user@*le.com",
+ "challenge": "ENSPAWJ0CNW62VBGDHJJWRVFDM50"
+ }
+ ]
+ }
+
+
+**delete_authentication**:
+
+This transition can be used to remove an authentication method from the
+array of authentication methods. It simply requires the index of the
+authentication method to remove. Note that the array is 0-indexed:
+
+.. code-block:: json
+
+ {
+ "authentication_method": 1
+ }
+
+Assuming we begin with the state from the example above, this would
+remove the ``email`` authentication method, resulting in the following
+response:
+
+.. code-block:: json
+
+ {
+ "backup_state": "AUTHENTICATIONS_EDITING",
+ "authentication_methods": [
+ {
+ "type": "question",
+ "mime_type" : "text/plain",
+ "instructions" : "What is your favorite GNU package?",
+ "challenge" : "gdb",
+ }
+ ]
+ }
+
+If the index is invalid, the reducer will instead
+transition into an ``ERROR`` state.
+
+
+**next** (from ``AUTHENTICATIONS_EDITING``):
+
+This transition confirms that the user has finished adding (or removing)
+authentication methods, and that the system should now automatically compute
+a set of reasonable recovery policies.
+
+This transition does not take any mandatory arguments. Optional arguments can
+be provided to upload the recovery document only to a specific subset of the
+providers:
+
+.. code-block:: json
+
+ {
+ "providers": [
+ "http://localhost:8088/",
+ "http://localhost:8089/"
+ ]
+ }
+
+The resulting state provides the suggested recovery policies in a way suitable
+for presentation to the user:
+
+.. code-block:: javascript
+
+ {
+ "backup_state": "POLICIES_REVIEWING",
+ "policy_providers" : [
+ { "provider_url" : "http://localhost:8088/" },
+ { "provider_url" : "http://localhost:8089/" }
+ ],
+ "policies": [
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8088/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8087/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8088/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 3,
+ "provider": "http://localhost:8089/"
+ }
+ ]
+ }
+ ]
+ }
+
+For each recovery policy, the state includes the specific details of which
+authentication ``methods`` must be solved to recovery the secret using this
+policy. The ``methods`` array specifies the index of the
+``authentication_method`` in the ``authentication_methods`` array, as well as
+the provider that was selected to supervise this authentication.
+
+If no authentication method was provided, the reducer will transition into an
+``ERROR`` state instead of suggesting policies.
+
+
+**add_policy**:
+
+Using this transition, the user can add an additional recovery policy to the
+state. The argument format is the same that is used in the existing state.
+An example for a possible argument would thus be:
+
+.. code-block:: javascript
+
+ {
+ "policy": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ },
+ {
+ "authentication_method": 3,
+ "provider": "http://localhost:8089/"
+ }
+ ]
+ }
+
+Note that the specified providers must already be in the
+``authentication_providers`` of the state. You cannot add new providers at
+this stage. The reducer will simply attempt to append the suggested policy to
+the "policies" array, returning an updated state:
+
+.. code-block:: json
+
+ {
+ "backup_state": "POLICIES_REVIEWING",
+ "policies": [
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ },
+ {
+ "authentication_method": 3,
+ "provider": "http://localhost:8089/"
+ }
+ ]
+ }
+ ]
+ }
+
+If the new policy is invalid, for example because it adds an unknown
+authentication method, or the selected provider does not support the type of
+authentication, the reducer will transition into an ``ERROR`` state instead of
+adding the new policy.
+
+
+**update_policy**:
+
+Using this transition, the user can modify an existing recovery policy
+in the state.
+The argument format is the same that is used in **add_policy**,
+except there is an additional key ``policy_index`` which
+identifies the policy to modify.
+An example for a possible argument would thus be:
+
+.. code-block:: javascript
+
+ {
+ "policy_index" : 1,
+ "policy": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ },
+ {
+ "authentication_method": 3,
+ "provider": "http://localhost:8089/"
+ }
+ ]
+ }
+
+If the new policy is invalid, for example because it adds an unknown
+authentication method, or the selected provider does not support the type of
+authentication, the reducer will transition into an ``ERROR`` state instead of
+modifying the policy.
+
+
+
+**delete_policy:**
+
+This transition allows the deletion of a recovery policy. The argument
+simply specifies the index of the policy to delete, for example:
+
+.. code-block:: json
+
+ {
+ "policy_index": 3
+ }
+
+Given as input the state from the example above, the expected new state would
+be:
+
+.. code-block:: json
+
+ {
+ "backup_state": "POLICIES_REVIEWING",
+ "policies": [
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ }
+ ]
+ }
+
+If the index given is invalid, the reducer will transition into an ``ERROR`` state
+instead of deleting a policy.
+
+
+**delete_challenge:**
+
+This transition allows the deletion of an individual
+challenge from a recovery policy. The argument
+simply specifies the index of the policy and challenge
+to delete, for example:
+
+.. code-block:: json
+
+ {
+ "policy_index": 1,
+ "challenge_index" : 1
+ }
+
+Given as input the state from the example above, the expected new state would
+be:
+
+.. code-block:: json
+
+ {
+ "backup_state": "POLICIES_REVIEWING",
+ "policies": [
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 0,
+ "provider": "http://localhost:8089/"
+ }
+ ]
+ },
+ {
+ "methods": [
+ {
+ "authentication_method": 1,
+ "provider": "http://localhost:8089/"
+ },
+ {
+ "authentication_method": 2,
+ "provider": "http://localhost:8088/"
+ }
+ ]
+ }
+ ]
+ }
+
+If the index given is invalid, the reducer will transition into an ``ERROR`` state
+instead of deleting a challenge.
+
+
+**next** (from ``POLICIES_REVIEWING``):
+
+Using this transition, the user confirms that the policies in the current
+state are acceptable. The transition does not take any arguments.
+
+The reducer will simply transition to the ``SECRET_EDITING`` state:
+
+.. code-block:: json
+
+ {
+ "backup_state": "SECRET_EDITING",
+ "upload_fees" : [ "KUDOS:42" ],
+ "expiration" : { "t_ms" : 1245362362 }
+ }
+
+Here, ``upload_fees`` is an array of applicable upload fees for the
+given policy expiration time. This is an array because fees could
+be in different currencies. The final cost may be lower if the
+user already paid for some of the time.
+
+If the array of ``policies`` is currently empty, the reducer will transition
+into an ``ERROR`` state instead of allowing the user to continue.
+
+
+**enter_secret:**
+
+This transition provides the reducer with the actual core ``secret`` of the user
+that Anastasis is supposed to backup (and possibly recover). The argument is
+simply the Crockford-Base32 encoded ``value`` together with its ``mime`` type, or a ``text`` field with a human-readable secret text.
+For example:
+
+.. code-block:: javascript
+
+ {
+ "secret": {
+ "value": "EDJP6WK5EG50",
+ "mime" : "text/plain"
+ },
+ "expiration" : { "t_ms" : 1245362362 }
+ }
+
+If the application is unaware of the format, it set the ``mime`` field to ``null``.
+The ``expiration`` field is optional.
+
+The reducer remains in the ``SECRET_EDITING`` state, but now the secret and
+updated expiration time are part of the state and the cost calculations will
+be updated.
+
+.. code-block:: json
+
+ {
+ "backup_state": "SECRET_EDITING",
+ "core_secret" : {
+ "value": "EDJP6WK5EG50",
+ "mime" : "text/plain"
+ },
+ "expiration" : { "t_ms" : 1245362362 },
+ "upload_fees" : [ "KUDOS:42" ]
+ }
+
+
+**clear_secret:**
+
+This transition removes the core secret from the state. It is simply a
+convenience function to undo ``enter_secret`` without providing a new value
+immediately. The transition takes no arguments. The resuting state will no
+longer have the ``core_secret`` field, and be otherwise unchanged. Calling
+**clear_secret** on a state without a ``core_secret`` will result in an error.
+
+
+**enter_secret_name:**
+
+This transition provides the reducer with a name for the core ``secret`` of the user. This name will be given to the user as a hint when seleting a recovery policy document during recovery, prior to satisfying any of the challenges. The argument simply contains the name for the secret.
+Applications that have built-in support for Anastasis MUST prefix the
+secret name with an underscore and an application-specific identifier
+registered in GANA so that they can use recognize their own backups.
+An example argument would be:
+
+.. code-block:: javascript
+
+ {
+ "name": "_TALERWALLET_MyPinePhone",
+ }
+
+Here, ``MyPinePhone`` might be chosen by the user to identify the
+device that was being backed up.
+
+The reducer remains in the ``SECRET_EDITING`` state, but now the
+secret name is updated:
+
+.. code-block:: json
+
+ {
+ "secret_name" : "_TALERWALLET_MyPinePhone"
+ }
+
+
+**update_expiration:**
+
+This transition asks the reducer to change the desired expiration time
+and to update the associated cost. For example:
+
+.. code-block:: javascript
+
+ {
+ "expiration" : { "t_ms" : 1245362362 }
+ }
+
+The reducer remains in the ``SECRET_EDITING`` state, but the
+expiration time and cost calculation will be updated.
+
+.. code-block:: json
+
+ {
+ "backup_state": "SECRET_EDITING",
+ "expiration" : { "t_ms" : 1245362362 },
+ "upload_fees" : [ { "fee": "KUDOS:43" } ]
+ }
+
+
+**next** (from ``SECRET_EDITING``):
+
+Using this transition, the user confirms that the secret and expiration
+settings in the current state are acceptable. The transition does not take any
+arguments.
+
+If the secret is currently empty, the reducer will transition into an
+``ERROR`` state instead of allowing the user to continue.
+
+After adding a secret, the reducer may transition into different states
+depending on whether payment(s) are necessary. If payments are needed, the
+``secret`` will be stored in the state under ``core_secret``. Applications
+should be careful when persisting the resulting state, as the ``core_secret``
+is not protected in the ``PAYING`` states. The ``PAYING`` states only differ
+in terms of what the payments are for (key shares or the recovery document),
+in all cases the state simply includes an array of Taler URIs that refer to
+payments that need to be made with the Taler wallet.
+
+If all payments are complete, the reducer will transition into the
+``BACKUP_FINISHED`` state and (if applicable) delete the ``core_secret`` as an
+additional safety measure.
+
+Example results are thus:
+
+.. code-block:: json
+
+ {
+ "backup_state": "TRUTHS_PAYING",
+ "secret_name" : "$NAME",
+ "core_secret" : { "$anything":"$anything" },
+ "payments": [
+ "taler://pay/...",
+ "taler://pay/..."
+ ]
+ }
+
+.. code-block:: json
+
+ {
+ "backup_state": "POLICIES_PAYING",
+ "secret_name" : "$NAME",
+ "core_secret" : { "$anything":"$anything" },
+ "payments": [
+ "taler://pay/...",
+ "taler://pay/..."
+ ]
+ }
+
+.. code-block:: json
+
+ {
+ "backup_state": "BACKUP_FINISHED",
+ "success_details": {
+ "http://localhost:8080/" : {
+ "policy_version" : 1,
+ "policy_expiration" : { "t_ms" : 1245362362000 }
+ },
+ "http://localhost:8081/" : {
+ "policy_version" : 3,
+ "policy_expiration" : { "t_ms" : 1245362362000 }
+ }
+ }
+ }
+
+
+**pay:**
+
+This transition suggests to the reducer that a payment may have been made or
+is immanent, and that the reducer should check with the Anastasis service
+provider to see if the operation is now possible. The operation takes one
+optional argument, which is a ``timeout`` value that specifies how long the
+reducer may wait (in long polling) for the payment to complete:
+
+.. code-block:: json
+
+ {
+ "timeout": { "d_ms" : 5000 },
+ }
+
+The specified timeout is passed on to the Anastasis service provider(s), which
+will wait this long before giving up. If no timeout is given, the check is
+done as quickly as possible without additional delays. The reducer will continue
+to either an updated state with the remaining payment requests, to the
+``BACKUP_FINISHED`` state (if all payments have been completed and the backup
+finished), or into an ``ERROR`` state in case there was an irrecoverable error,
+indicating the specific provider and how it failed. An example for this
+final error state would be:
+
+.. code-block:: json
+
+ {
+ "backup_state": "ERROR",
+ "http_status" : 500,
+ "upload_status" : 52,
+ "provider_url" : "https://bad.example.com/",
+ }
+
+Here, the fields have the following meaning:
+
+ - **http_status** is the HTTP status returned by the Anastasis provider.
+ - **upload_status** is the Taler error code return by the provider.
+ - **provider_url** is the base URL of the failing provider.
+
+In the above example, 52 would thus imply that the Anastasis provider failed to
+store information into its database.
+
+
+Recovery transitions
+--------------------
+
+**enter_user_attributes:**
+
+This transition provides the user's personal attributes. The specific set of
+attributes required depends on the country of residence of the user. Some
+attributes may be optional, in which case they should be omitted entirely
+(that is, not simply be set to ``null`` or an empty string). The
+arguments are identical to the **enter_user_attributes** transition from
+the backup process. Example arguments would thus be:
+
+.. code-block:: json
+
+ {
+ "identity_attributes": {
+ "full_name": "Max Musterman",
+ "social_security_number": "123456789",
+ "birthdate": "2000-01-01",
+ "birthplace": "Earth"
+ }
+ }
+
+However, in contrast to the backup process, the reducer will attempt to
+retrieve the latest recovery document from all known providers for the
+selected currency given the above inputs. If a recovery document was found
+by any provider, the reducer will attempt to load it and transition to
+a state where the user can choose which challenges to satisfy:
+
+.. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "challenges": [
+ {
+ "uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G",
+ "cost": "TESTKUDOS:0",
+ "type": "question",
+ "instructions": "q1"
+ },
+ {
+ "uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "cost": "TESTKUDOS:0",
+ "type": "email",
+ "instructions": "e-mail address m?il@f*.bar"
+ },
+ ],
+ "policies": [
+ [
+ {
+ "uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G"
+ },
+ {
+ "uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0"
+ },
+ ],
+ ],
+ "provider_url": "http://localhost:8088/",
+ "version": 1,
+ },
+ "recovery_document": {
+ "...": "..."
+ }
+ }
+
+The ``recovery_document`` is an internal representation of the recovery
+information and of no concern to the user interface. The pertinent information
+is in the ``recovery_information``. Here, the ``challenges`` array is a list
+of possible challenges the user could attempt to solve next, while ``policies``
+is an array of policies, with each policy being an array of challenges.
+Satisfying all of the challenges of one of the policies will enable the secret
+to be recovered. The ``provider_url`` from where the recovery document was
+obtained and its ``version`` are also provided. Each challenge comes with
+four mandatory fields:
+
+ - **uuid**: A unique identifier of the challenge; this is what the
+ UUIDs in the policies array refer to, but also this UUID may be
+ included in messages sent to the user. They allow the user to
+ distinguish different PIN/TANs should say the same phone number be
+ used for SMS-authentication with different providers.
+ - **cost**: This is the amount the Anastasis provider will charge
+ to allow the user to pass the challenge.
+ - **type**: This is the type of the challenge, as a string.
+ - **instructions**: Contains additional important hints for the user
+ to allow the user to satisfy the challenge. It typically includes
+ an abbreviated form of the contact information or the security
+ question. Details depend on ``type``.
+
+If a recovery document was not found, either the user never performed
+a backup, entered incorrect attributes, or used a provider not yet in
+the list of Anastasis providers. Hence, the user must now either
+select a different provider, or go ``back`` and update the identity
+attributes. In the case a recovery document was not found, the
+transition fails, returning the error code and a human-readable error
+message together with a transition failure:
+
+.. code-block:: json
+
+ {
+ "recovery_state": "ERROR",
+ "error_message": "account unknown to Anastasis server",
+ "error_code": 9,
+ }
+
+Here, the ``error_code`` is from the ``enum ANASTASIS_RecoveryStatus``
+and describes precisely what failed about the download, while the
+``error_message`` is a human-readable (English) explanation of the code.
+Applications may want to translate the message using GNU gettext;
+translations should be available in the ``anastasis`` text domain.
+However, in general it should be sufficient to display the slightly
+more generic Taler error code that is returned with the new state.
+
+
+**change_version:**
+
+Even if a recovery document was found, it is possible that the user
+intended to recover a different version, or recover a backup where
+the recovery document is stored at a different provider. Thus, the
+reducer allows the user to explicitly switch to a different provider
+or recovery document version using the ``change_version`` transition,
+which takes a provider URL and policy version as arguments:
+
+.. code-block:: json
+
+ {
+ "provider_url": "https://localhost:8080/",
+ "version": 2
+ }
+
+Note that using a version of 0 implies fetching "the latest version". The
+resulting states are the same as those of the ``enter_user_attributes``
+transition, except that the recovery document version is not necessarily the
+latest available version at the provider.
+
+
+**select_challenge:**
+
+Selecting a challenge takes different, depending on the state of the payment.
+A comprehensive example for ``select_challenge`` would be:
+
+.. code-block:: json
+
+ {
+ "uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30"
+ "timeout" : { "d_ms" : 5000 },
+ "payment_secret": "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG"
+ }
+
+The ``uuid`` field is mandatory and specifies the selected challenge.
+The other fields are optional, and are needed in case the user has
+previously been requested to pay for the challenge. In this case,
+the ``payment_secret`` identifies the previous payment request, and
+``timeout`` says how long the Anastasis service should wait for the
+payment to be completed before giving up (long polling).
+
+Depending on the type of the challenge and the need for payment, the
+reducer may transition into ``CHALLENGE_SOLVING`` or ``CHALLENGE_PAYING``
+states. In ``CHALLENGE_SOLVING``, the new state will primarily specify
+the selected challenge:
+
+.. code-block:: json
+
+ {
+ "backup_state": "CHALLENGE_SOLVING",
+ "selected_challenge_uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30"
+ }
+
+In ``CHALLENGE_PAYING``, the new state will include instructions for payment
+in the ``challenge_feedback``. In general, ``challenge_feedback`` includes
+information about attempted challenges, with the final state being ``solved``:
+
+.. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "challenge_feedback": {
+ "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" : {
+ "state" : "solved"
+ }
+ }
+ }
+
+Challenges feedback for a challenge can have many different ``state`` values
+that applications must all handle. States other than ``solved`` are:
+
+ - **payment**: Here, the user must pay for a challenge. An example would be:
+
+ .. code-block:: json
+
+ {
+ "backup_state": "CHALLENGE_PAYING",
+ "selected_challenge_uuid": "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30",
+ "challenge_feedback": {
+ "80H646H5ZBR453C02Y5RT55VQSJZGM5REWFXVY0SWXY1TNE8CT30" : {
+ "state" : "payment",
+ "taler_pay_uri" : "taler://pay/...",
+ "provider" : "https://localhost:8080/",
+ "payment_secret" : "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG"
+ }
+ }
+ }
+
+ - **body**: Here, the server provided an HTTP reply for
+ how to solve the challenge, but the reducer could not parse
+ them into a known format. A mime-type may be provided and may
+ help parse the details.
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "body",
+ "body": "CROCKFORDBASE32ENCODEDBODY",
+ "http_status": 403,
+ "mime_type" : "anything/possible"
+ }
+ }
+ }
+
+ - **hint**: Here, the server provided human-readable hint for
+ how to solve the challenge. Note that the ``hint`` provided this
+ time is from the Anastasis provider and may differ from the ``instructions``
+ for the challenge under ``recovery_information``:
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "hint",
+ "hint": "Recovery TAN send to email mail@DOMAIN",
+ "http_status": 403
+ }
+ }
+ }
+
+ - **details**: Here, the server provided a detailed JSON status response
+ related to solving the challenge:
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "details",
+ "details": {
+ "code": 8111,
+ "hint": "The client's response to the challenge was invalid.",
+ "detail" : null
+ },
+ "http_status": 403
+ }
+ }
+ }
+
+ - **redirect**: To solve the challenge, the user must visit the indicated
+ Web site at ``redirect_url``, for example to perform video authentication:
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SOLVING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "redirect",
+ "redirect_url": "https://videoconf.example.com/",
+ "http_status": 303
+ }
+ }
+ }
+
+ - **server-failure**: This indicates that the Anastasis provider encountered
+ a failure and recovery using this challenge cannot proceed at this time.
+ Examples for failures might be that the provider is unable to send SMS
+ messages at this time due to an outage. The body includes details about
+ the failure. The user may try again later or continue with other challenges.
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "server-failure",
+ "http_status": "500",
+ "error_code": 52
+ }
+ }
+ }
+
+ - **truth-unknown**: This indicates that the Anastasis provider is unaware of
+ the specified challenge. This is typically a permanent failure, and user
+ interfaces should not allow users to re-try this challenge.
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "truth-unknown",
+ "error_code": 8108
+ }
+ }
+ }
+
+ - **rate-limit-exceeded**:
+
+ .. code-block:: json
+
+ {
+ "recovery_state": "CHALLENGE_SELECTING",
+ "recovery_information": {
+ "...": "..."
+ }
+ "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
+ "challenge_feedback": {
+ "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
+ "state": "rate-limit-exceeded",
+ "error_code": 8121
+ }
+ }
+ }
+
+**pay:**
+
+With a ``pay`` transition, the application indicates to the reducer that
+a payment may have been made. Here, it is again possible to specify an
+optional ``timeout`` argument for long-polling, for example:
+
+.. code-block:: json
+
+ {
+ "payment_secret": "ABCDADF242525AABASD52525235ABABFDABABANALASDAAKASDAS"
+ "timeout" : { "d_ms" : 5000 },
+ }
+
+Depending on the type of the challenge and the result of the operation, the
+new state may be ``CHALLENGE_SOLVING`` (if say the SMS was now sent to the
+user), ``CHALLENGE_SELECTING`` (if the answer to the security question was
+correct), ``RECOVERY_FINISHED`` (if this was the last challenge that needed to
+be solved) or still ``CHALLENGE_PAYING`` (if the challenge was not actually
+paid for). For sample messages, see the different types of
+``challenge_feedback`` in the section about ``select_challenge``.
+
+
+**solve_challenge:**
+
+Solving a challenge takes various formats, depending on the type of the
+challenge and what is known about the answer. The different supported
+formats are:
+
+.. code-block:: json
+
+ {
+ "answer": "answer to security question"
+ }
+
+.. code-block:: json
+
+ {
+ "pin": 1234
+ }
+
+.. code-block:: json
+
+ {
+ "hash": "SOMEBASE32ENCODEDHASHVALUE"
+ }