6. 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:

{
  "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:

{
  "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:

{
  "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"
    }
  ]
}

An action may also result into an error response instead of a new state. Clients should then render this error response to the user and allow the user to continue from the old state. An error response looks like this:

{
  "code": 123,
  "hint": "something went wrong",
  "details": "parameter foo failed to frobnify"
}

6.1. States

Overall, the reducer knows the following states:

  • 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.

6.2. Backup Reducer

fig-anastasis_reducer_backup

Backup states and their transitions.

The illustration above shows the different states the reducer can have during a backup process.

6.3. Recovery Reducer

fig-anastasis_reducer_recovery

Recovery states and their transitions.

The illustration above shows the different states the reducer can have during a recovery process.

6.4. 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.

6.4.1. Initial state

The initial states for backup and recovery processes are:

Initial backup state:

{
  "backup_state": "CONTINENT_SELECTING",
  "continents": [
    "Europe",
    "North America"
  ]
}

Initial recovery state:

{
  "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:

{
  "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.

6.4.2. Common transitions

select_continent:

Here the user specifies the continent they live on. Arguments (example):

{
  "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:

{
  "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):

{
  "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:

{
  "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 object with the base URLs of the providers to add or disable. The object maps the URLs to status information about the provider to use. For example:

{
  "http://localhost:8088/" : { "disabled" : false },
  "http://localhost:8089/" : { "disabled" : false },
  "http://localhost:8090/" : { "disabled" : true }
}

Note that existing providers will remain in the state they were in. The following is an example for an expected new state where the service on port 8089 is unreachable, the services on port 8088 and 8888 were previously known, and service on port 8088 was now added, and on 8090 is disabled:

{
  "backup_state": "USER_ATTRIBUTES_COLLECTING",
  "authentication_providers": {
    "http://localhost:8089/": {
      "disabled": false,
      "error_code": 11,
      "http_status": 0
    },
    "http://localhost:8090/": {
      "disabled": true
    },
    "http://localhost:8088/": {
      "disabled": false,
      "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"
    }
  }
}

6.4.3. 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:

{
  "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:

{
  "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 return an error response indicating that the transition has failed and what is wrong about the input and not transition to a new state. 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": 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.

{
  "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:

{
  "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:

{
  "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:

{
  "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 return an error response instead of making a transition.

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:

{
  "providers": [
    "http://localhost:8088/",
    "http://localhost:8089/"
  ]
}

The resulting state provides the suggested recovery policies in a way suitable for presentation to the user:

{
  "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 return an error response instead of making a transition.

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:

{
  "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:

{
  "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 return an error response 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:

{
  "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 return an error response 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:

{
  "policy_index": 3
}

Given as input the state from the example above, the expected new state would be:

{
  "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 return an error response 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:

{
  "policy_index": 1,
  "challenge_index" : 1
}

Given as input the state from the example above, the expected new state would be:

{
  "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 return an error response 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:

{
  "backup_state": "SECRET_EDITING",
  "upload_fees" : [ { "fee": "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 return an error response 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:

{
  "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.

{
  "backup_state": "SECRET_EDITING",
  "core_secret" : {
     "value": "EDJP6WK5EG50",
     "mime" : "text/plain"
  },
  "expiration" : { "t_ms" : 1245362362 },
  "upload_fees" : [ { "fee": "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:

{
  "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:

{
  "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:

{
  "expiration" : { "t_ms" : 1245362362 }
}

The reducer remains in the SECRET_EDITING state, but the expiration time and cost calculation will be updated.

{
  "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 return an error response 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:

{
  "backup_state": "TRUTHS_PAYING",
  "secret_name" : "$NAME",
  "core_secret" : { "$anything":"$anything" },
  "payments": [
    "taler://pay/...",
    "taler://pay/..."
  ]
}
{
  "backup_state": "POLICIES_PAYING",
  "secret_name" : "$NAME",
  "core_secret" : { "$anything":"$anything" },
  "payments": [
    "taler://pay/...",
    "taler://pay/..."
  ]
}
{
  "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:

{
  "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 return an error response in case there was an irrecoverable error, indicating the specific provider and how it failed. An example for this final error state would be:

{
  "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.

6.4.4. 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:

{
  "identity_attributes": {
    "full_name": "Max Musterman",
    "social_security_number": "123456789",
    "birthdate": "2000-01-01",
    "birthplace": "Earth"
  }
}

Afterwards, the reducer transitions into the SECRET_SELECTING state:

{
  "recovery_state": "SECRET_SELECTING",
  "identity_attributes": {
    "full_name": "Max Musterman",
    "social_security_number": "123456789",
    "birthdate": "2000-01-01",
    "birthplace": "Earth"
  }
}

Typically, the special policy discovery process (outside of the state machine) is expected to be run in this state. The discovery process will use the state (and in particular the identity attributes and the list of active providers) to discover a set of possible recovery documents with their respective provider URLs, policy version and identity attribute mask. An identity attribute mask is a bitmask that describes which of the optional attributes from the identity attributes should be omitted to recover this backup. Once the user has selected a backup providing this triplet, it is possible to proceed using next.

Especially if the discovered policies are inadequate, it is again possible to add providers using add_provider.

add_provider:

This operation can be performed in state SECRET_SELECTING. It adds one additional Anastasis provider to the list of providers that the discovery process should henceforth consider. Note that removing providers is not possible at this time.

Here, the client must provide an object with the base URL of the providers to add, for example:

{
  "provider_url" : "http://localhost:8088/"
}

select_version:

Using the select_version transition in the SECRET_SELECTING state, it is possible to trigger the download and decryption of a recovery policy document. Here, the arguments specify which provider, version and mask should be used to download the document:

{
  "providers" : [ {
    "url": "https://localhost:8088/",
    "version": 0
  } ],
  "attribute_mask": 0
}

The reducer will attempt to retrieve the specified recovery document from that provider. If a recovery document was found, the reducer will attempt to load it and transition to a state where the user can choose which challenges to satisfy:

{
  "recovery_state": "CHALLENGE_SELECTING",
  "recovery_information": {
    "challenges": [
      {
        "uuid": "MW2R3RCBZPHNC78AW8AKWRCHF9KV3Y82EN62T831ZP54S3K5599G",
        "uuid-display": "MW2R3RC",
        "type": "question",
        "instructions": "q1"
      },
      {
        "uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
        "uuid-display": "TXYKGE",
        "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.
  • uuid-display: Shortened idenfier which is included in messages send to the user. Allows the user to distinguish different PIN/TANs should say the same phone number be used for SMS-authentication with different providers.
  • 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:

{
  "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.

sync_providers

The downloaded policy may include secrets from providers for which we do not (yet) have the cost structure or even the salt. So here an application can use the sync_providers request to download /config from providers that are in the challenge list but not yet known with their salt and other attributes in the provider list.

The transition fails if all providers relevant for the selected policy are already downloaded. Applications may either internally check the state for this, or call sync_providers until it fails with this error:

{
  "detail": "already in sync",
  "code": 8400,
  "hint": "The given action is invalid for the current state of the reducer."
}

As providers may fail to respond, this action may need to be called repeatedly. The action will block until progress is made on any provider. As some providers may never respond, the application should disable challenge buttons for challenges where providers are down. However, users should be able to solve challenges where the provider is up while the reducer is polling for /config in the background.

select_challenge:

Selecting a challenge takes different, depending on the state of the payment. A comprehensive example for select_challenge would be:

{
    "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:

{
  "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:

{
  "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:

    {
      "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.

    {
      "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:

    {
      "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:

    {
      "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:

    {
      "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.

     {
       "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.

    {
      "recovery_state": "CHALLENGE_SELECTING",
      "recovery_information": {
          "...": "..."
      }
      "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
      "challenge_feedback": {
        "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
          "state": "truth-unknown",
          "error_code": 8108
        }
      }
    }
    
  • rate-limit-exceeded: This indicates that the user has made too many invalid attempts in too short an amount of time.

    {
      "recovery_state": "CHALLENGE_SELECTING",
      "recovery_information": {
          "...": "..."
      }
      "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
      "challenge_feedback": {
        "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
          "state": "rate-limit-exceeded",
          "error_code": 8121
        }
      }
    }
    
  • authentication-timeout: This indicates that the challenge is awaiting for some external authentication process to complete. The application should poll for it to complete, or proceed with selecting other challenges.

    {
      "recovery_state": "CHALLENGE_SELECTING",
      "recovery_information": {
          "...": "..."
      }
      "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
      "challenge_feedback": {
        "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
          "state": "authentication-timeout",
          "error_code": 8122
        }
      }
    }
    
  • external-instructions: This indicates that the challenge requires the user to perform some authentication method-specific actions. Details about what the user should do are provided.

    {
      "recovery_state": "CHALLENGE_SELECTING",
      "recovery_information": {
          "...": "..."
      }
      "selected_challenge_uuid": "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0",
      "challenge_feedback": {
        "TXYKGE1SJZHJ4M2FKSV1P2RZVNTHZFB9E3A79QE956D3SCAWXPK0": {
          "state": "external-instructions",
          "method": "iban",
          "async": true, // optional
          "answer_code": 987654321, // optional
          "details": {
            "...": "..."
          }
        }
      }
    }
    

    If “async” is “true”, then the client should poll for the challenge being satisfied using the “answer_code” that has been provided.

    The specific instructions on how to satisfy the challenge depend on the method. They include:

    • iban: The user must perform a wire transfer from their account to the Anastasis provider.

      {
        "challenge_amount": "EUR:1",
        "credit_iban": "DE12345789000",
        "business_name": "Data Loss Incorporated",
        "wire_transfer_subject": "Anastasis 987654321"
      }
      

      Note that the actual wire transfer subject must contain both the numeric answer_code as well as the string Anastasis.

poll:

With a poll transition, the application indicates that it wants to wait longer for one or more of the challenges that are awaiting some external authentication (state external-instructions) or experienced some kind of timeout (state authentication-timeout) to possibly complete. While technically optional, the timeout argument should really be provided to enable long-polling, for example:

{
    "timeout" : { "d_ms" : 5000 },
}

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:

{
    "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:

{
    "answer": "answer to security question"
}
{
    "pin": 1234
}
{
    "hash": "SOMEBASE32ENCODEDHASHVALUE"
}