taler-docs

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

092-incremental-backup-sync.rst (110271B)


      1 ===========================================
      2  DD 92: Incremental Wallet Backup and Sync
      3 ===========================================
      4 
      5 Summary
      6 =======
      7 
      8 This design document describes an incremental, CRDT-based, encrypted wallet
      9 backup and sync protocol that addresses the limitations of previous solutions.
     10 
     11 Motivation
     12 ==========
     13 
     14 An encrypted backup and sync protocol for wallets was the subject of three
     15 design documents (`DD05`_, `DD09`_ and `DD19`_), in which considerations for
     16 different aspects of backup and sync, as well as limitations of the proposed
     17 designs, were discussed and documented, ultimately resulting in a
     18 proof-of-concept server and wallet implementation.
     19 
     20 .. _DD05: https://docs.taler.net/design-documents/005-wallet-backup-sync.html
     21 .. _DD09: https://docs.taler.net/design-documents/009-backup.html
     22 .. _DD19: https://docs.taler.net/design-documents/019-wallet-backup-merge.html
     23 
     24 In the original design, an object containing a set of data entities managed by
     25 the wallet is serialized, gzip-compressed, kilobyte-padded and encrypted using
     26 libsodium's `secretbox`_ function using a symmetric key derived from the
     27 wallet's root key and a salt.
     28 
     29 .. _secretbox: https://libsodium.gitbook.io/doc/secret-key_cryptography/secretbox
     30 
     31 The resulting block is then uploaded to a sync server configured in the
     32 wallet, where it can be later recovered by another wallet and decrypted. It is
     33 at this point where conflicts with the existing database are resolved on a
     34 last-write-wins CRDT fashion, favoring deletion in concurrent, conflicting
     35 insert/delete operations.
     36 
     37 Since the data entities contained in the backup represent the state of the
     38 entire database at a given timestamp, the backup and restore operations
     39 described are not incremental and therefore not practical for synchronization
     40 between multiple devices, as the database can grow in size indefinitely,
     41 slowing down backup and restore operations over time.
     42 
     43 The revised solution proposed in this design document aims to address the
     44 limitations of the previous design by introducing an incremental, CRDT-based,
     45 end-to-end-encrypted wallet backup and sync protocol that is robust,
     46 efficient, reliable, and suitable for use between multiple devices.
     47 
     48 Requirements
     49 ============
     50 
     51 * **Confidenciality/E2EE:** No information about the contents of the wallets
     52   should be accessible or derivable by any third-party who lacks control over
     53   the wallet, including the backup service. Any potential metadata
     54   leakage—such as backup file sizes, upload frequencies, or timing
     55   patterns—should be minimized to the highest extent possible.
     56 * **Incrementality:** The solution should minimize network usage and bandwidth
     57   by incrementally uploading and fetching updates to the global state when
     58   possible, limiting the situations where a full backup or restore is
     59   required.
     60 * **Plausible deniability:** The solution should ensure that no information
     61   can be decrypted or retrieved from the backup after its deletion, including
     62   the evidence that such information was deleted.
     63 
     64 .. _threat-model:
     65 
     66 Threat model
     67 ============
     68 
     69 The design protects the confidentiality of the wallet's backup contents
     70 against any party that does not hold the wallet's backup encryption key,
     71 including the backup service itself. Blocks and blobs are end-to-end encrypted
     72 with keys derived from secrets that only the user's wallets know, so neither a
     73 passive network observer nor the operator of the backup service can learn
     74 anything about the contents of a backup from the data they can access.
     75 
     76 Within this model, the backup service is trusted to honor deletion requests
     77 and to not retain deleted blocks nor previous versions of updated blocks. The
     78 protocol does **not** defend against a service that fails to do so: while such
     79 a service still cannot decrypt the retained data, it can defeat the plausible
     80 deniability requirement by preserving evidence that certain information once
     81 existed in the backup, and countering this would be impractical for an
     82 incremental, multi-device protocol. Users must therefore trust the sync server
     83 operator in such cases, as well as to refrain from misusing the metadata that
     84 the protocol necessarily exposes to it (see :ref:`limitations`).
     85 
     86 Proposed solution
     87 =================
     88 
     89 Backup and synchronization service
     90 ----------------------------------
     91 
     92 Insertions and updates to objects in the wallet database are collected in a
     93 temporary buffer. Certain events in schedules in the wallet trigger the
     94 incremental backup process, where this buffer is serialized, encrypted into a
     95 kilobyte-padded block, assigned a random UUID, and finally uploaded to the
     96 backup service, along with the UUIDs of the previous and next block (when
     97 applicable), and the hashes of all the large binary objects (blob) that are
     98 referenced in the batch, which are expected to be encrypted and uploaded
     99 beforehand to a separate hash-indexed object store.
    100 
    101 .. graphviz::
    102 
    103    digraph G {
    104        subgraph block {
    105            {
    106                rank = same
    107                "Block 0" [shape=box]
    108                "Block 1" [shape=box]
    109                "Block 2" [shape=box]
    110            }
    111 
    112            "Block 0" -> "Block 1"
    113            "Block 1" -> "Block 0"
    114            "Block 1" -> "Block 2"
    115            "Block 2" -> "Block 1"
    116 
    117            {
    118                rank = same
    119                first [shape=plaintext]
    120                last [shape=plaintext]
    121            }
    122 
    123            first -> "Block 0"
    124            last -> "Block 2"
    125        }
    126 
    127        node [shape=record]
    128        hash [label="{<f0> 197d605 | <f1> 409f945 | <f2> 8103756} | {<g0> 1 | <g1> 0 | <g2> 2} | {<h0> \<blob\> | <h1> \<blob\> | <h2> \<blob\>}"]
    129 
    130        edge [style=dotted]
    131        "Block 0" -> hash:f0 [constraint=false]
    132        "Block 1" -> hash:f2 [constraint=false]
    133        "Block 2" -> hash:f2 [constraint=false]
    134    }
    135 
    136 Double-linked list block store
    137 ------------------------------
    138 
    139 The sync server maintains a double-linked list in its database, as well as
    140 references to the global first and last block (useful for full restores). Via
    141 INSERT, DELETE and REPLACE operations, as well as a signature to authenticate
    142 the operation, wallets can upload blocks and manipulate the linked list in
    143 accordance with their internal CRDT logic.
    144 
    145 The sync server itself makes no decisions based on the content of the blocks,
    146 since it can only see them in their encrypted form. Wallets must therefore
    147 maintain a local, unencrypted version of the block store by fetching missing
    148 blocks from the server and assembling them in the correct order, verifying
    149 block signatures in the process in order to detect tampering or corruption.
    150 
    151 Furthermore, wallets are responsible of ensuring that all deletion operations
    152 provide plausible deniability by retroactively redacting the deleted objects
    153 from all the blocks where they appear or are referenced, and uploading the
    154 changes to the sync server, which is in turn trusted (see :ref:`threat-model`)
    155 to honor deletion requests and not retain any deleted blocks nor previous
    156 versions of updated blocks.
    157 
    158 During the synchronization process, wallets can either download the entirety
    159 of the linked list (full sync), or fetch only the missing and updated blocks
    160 by comparing their contents with the ones in the sync server by means of a
    161 reconciliation mechanism (read :ref:`sync-data-structures`).
    162 
    163 Block format
    164 ~~~~~~~~~~~~
    165 
    166 Each block consists of a 2-byte version number, a random 24-byte nonce, an
    167 8-byte serial, and a gzip-compressed JSON object with its length. The block is
    168 be padded up to the next whole kilobyte for privacy reasons. A block whose
    169 length is already a multiple of a kilobyte is not padded further.
    170 
    171 The nonce is 24 bytes because that is exactly what `secretbox`_ takes, which
    172 lets a block be encrypted under its own nonce.
    173 
    174 The serial is only ever seen by the wallets: it sits inside the encrypted
    175 payload, so the sync server knows nothing about it.  Wallets assign it on
    176 every content write (append or in-place update) as the account's maximum
    177 known serial plus one; relinking a block never changes its data and therefore
    178 never its serial.  A wallet checks the serial when it decrypts a block and
    179 refuses to apply a block whose serial is lower than the last one it saw for
    180 that block, which makes a rolled-back (replayed) block detectable.
    181 
    182 Encryption is performed on the block using symmetric authenticated encryption
    183 via libsodium's `secretbox`_ function, with a 32-byte key derived from the
    184 wallet's backup encryption key and the nonce of the block, which in the final
    185 implementation should be shareable between any wallets that the user wishes to
    186 add to the synchronization group.
    187 
    188 .. note::
    189 
    190    The key is derived from the *nonce* rather than from the hash of the
    191    plaintext block: the nonce travels with the block, whereas the plaintext
    192    hash is only known to whoever can already decrypt it, so deriving from it
    193    would make the block undecryptable.
    194 
    195 .. code-block:: text
    196 
    197    +----------------------------+
    198    | version number (2 byte)    |
    199    +----------------------------+
    200    | nonce (24 byte)            |
    201    +----------------------------+
    202    | serial (8 byte)            |
    203    +----------------------------+
    204    | JSON length n (4 byte)     |
    205    +----------------------------+
    206    | gzipped JSON (n byte)      |
    207    +----------------------------+
    208    | padding (to next full KB)  |
    209    +----------------------------+
    210 
    211 Block store API
    212 ~~~~~~~~~~~~~~~
    213 
    214 The account key is the base32-encoded Crockford representation of an EdDSA
    215 public key that identifies the backup account.  All upload requests must be
    216 signed by the corresponding private key; the signature is transmitted in the
    217 request body.
    218 
    219 Binary values in URLs, headers and JSON bodies (nonces, UIDs, hashes,
    220 signatures and the encrypted payloads themselves) are all base32-encoded in
    221 Crockford representation, as is usual for Taler.
    222 
    223 Signatures use EdDSA with the account private key.  Each signature payload
    224 follows the common Taler signing structure with a ``purpose`` field (see
    225 :ref:`Signatures` in the API common conventions for the general format).  The
    226 specific payloads are:
    227 
    228 .. sourcecode:: c
    229 
    230    /**
    231     * Purpose: TALER_SIGNATURE_SYNC_BLOCK_UPLOAD (1452)
    232     * Authorizes the append or in-place update of a block.
    233     * For appends, old_hash is all-zeros.
    234     */
    235    struct SyncBlockUploadSignaturePS {
    236      struct GNUNET_CRYPTO_SignaturePurpose purpose;
    237      struct SYNC_BlockNonce prev_nonce;  ///< all-zeros if first block
    238      struct SYNC_BlockNonce next_nonce;  ///< all-zeros if last block
    239      struct SYNC_BlockNonce nonce;
    240      struct GNUNET_HashCode old_hash;    ///< all-zeros for appends
    241      struct GNUNET_HashCode new_hash;
    242      struct GNUNET_HashCode refs_hash;   ///< over object_refs, see below
    243    };
    244 
    245    /**
    246     * Purpose: TALER_SIGNATURE_SYNC_BLOCK_DELETE (1453)
    247     * Authorizes the deletion of a block.
    248     */
    249    struct SyncBlockDeleteSignaturePS {
    250      struct GNUNET_CRYPTO_SignaturePurpose purpose;
    251      struct SYNC_BlockNonce nonce;
    252      struct SYNC_BlockNonce prev_nonce;  ///< all-zeros if first block
    253      struct SYNC_BlockNonce next_nonce;  ///< all-zeros if last block
    254      struct GNUNET_HashCode hash;
    255      struct GNUNET_HashCode refs_hash;   ///< over object_refs, see below
    256    };
    257 
    258    /**
    259     * Purpose: TALER_SIGNATURE_SYNC_OBJECT_UPLOAD (1454)
    260     * Authorizes the upload of a blob object.
    261     */
    262    struct SyncObjectUploadSignaturePS {
    263      struct GNUNET_CRYPTO_SignaturePurpose purpose;
    264      struct SYNC_ObjectUID uid;
    265      struct GNUNET_HashCode hash;
    266    };
    267 
    268 Absent optional nonces (``prev_nonce`` / ``next_nonce``) are treated as
    269 all-zeros in the signed data.
    270 
    271 The ``refs_hash`` field covers the ``object_refs`` of the request, so that the
    272 reference-count adjustments cannot be altered in transit.  It is the SHA-512
    273 hash over a canonical *binary* encoding of the references — not over their
    274 JSON representation.
    275 
    276 Each reference is laid out as the 64 raw UID bytes followed by the adjustment
    277 as a signed 16-bit integer in network byte order, and the resulting 66-byte
    278 records are concatenated in ascending order of UID:
    279 
    280 .. code-block:: text
    281 
    282    +----------------------------+
    283    | uid (64 byte)              |
    284    +----------------------------+
    285    | adjustment (2 byte, int16) |
    286    +----------------------------+
    287 
    288 Sorting by UID is required because ``object_refs`` travels as a JSON object,
    289 whose member order is not preserved.  A request without any references hashes
    290 the empty byte string.
    291 
    292 A UID may appear at most once, since the wire format keys the references by
    293 UID and could not otherwise transmit them faithfully.
    294 
    295 The server stores the ``upload_sig`` with the block, together with the rest of
    296 the signed context (``old_hash`` and ``refs_hash``), and returns them in the
    297 block list.  A wallet therefore verifies every block's stored signature
    298 against the account key before applying it; a block whose signature does not
    299 verify must not be applied.
    300 
    301 Operations that rewrite the links of an existing block (an append relinks the
    302 previous tail, a delete relinks both of its neighbours) require that block's
    303 *new* signature to be uploaded along with the operation.  This is an ordinary
    304 ``TALER_SIGNATURE_SYNC_BLOCK_UPLOAD`` signature over the relinked block's new
    305 nonces, carried in the ``relink_prev`` / ``relink_next`` fields of the
    306 request.  The server verifies it against the current state of the relinked
    307 block and stores it in the block's row; relinking never changes the block's
    308 data, so the signature's ``old_hash`` and ``new_hash`` are both the block's
    309 stored hash.
    310 
    311 .. http:get:: /config
    312 
    313    Return the server's protocol version and terms.  Requires no account
    314    and no signature.
    315 
    316    **Response**
    317 
    318    :http:statuscode:`200 OK`:
    319      The body is a `SyncConfig` object.
    320 
    321    .. ts:def:: SyncConfig
    322 
    323       interface SyncConfig {
    324         name: "sync";
    325         implementation: string;
    326         storage_limit_in_megabytes: number;
    327         liability_limit: AmountString;
    328         annual_fee: AmountString;
    329         version: string;
    330       }
    331 
    332    ``storage_limit_in_megabytes`` is the per-upload limit enforced for both
    333    blocks and objects; exceeding it yields ``413``.  ``version`` follows the
    334    Taler ``current:revision:age`` convention.
    335 
    336 .. http:get:: /backups/${ACCOUNT_KEY}
    337 
    338    Report the state of the account: when it expires, and how much of the
    339    storage allowance its backup uses.  Requires no signature, like the other
    340    read endpoints -- the account public key is the capability, and the stored
    341    data is client-encrypted.
    342 
    343    This is the only endpoint that answers for an expired account rather than
    344    demanding payment: when the account expires is precisely what the caller is
    345    asking, so a ``402`` here would be useless.  Wallets use it to tell the
    346    user how long the backup is paid for without waiting for the next write to
    347    fail.
    348 
    349    **Response**
    350 
    351    :http:statuscode:`200 OK`:
    352      The body is a `SyncAccountStatus` object.  Returned even when
    353      ``expiration_date`` lies in the past.
    354    :http:statuscode:`404 Not found`:
    355      The server does not know this account at all.  It has never been
    356      paid for, so there is no expiry to report.
    357 
    358    .. ts:def:: SyncAccountStatus
    359 
    360       interface SyncAccountStatus {
    361         // When the account expires, or expired.  Every other endpoint
    362         // answers 402 past this point.
    363         expiration_date: Timestamp;
    364 
    365         // Total size of the account's stored blocks, in bytes.
    366         storage_used_bytes: number;
    367 
    368         // Number of blocks in the account's linked list.
    369         block_count: number;
    370       }
    371 
    372 .. http:get:: /backups/${ACCOUNT_KEY}/blocks
    373 
    374    List blocks from the account's linked list with pagination.
    375 
    376    **Request**
    377 
    378    :query limit:
    379      *Required.* Maximum number of blocks to return.  Must be a positive
    380      count (int16).
    381    :query start_nonce:
    382      Optional nonce of the block from which to start listing.  If omitted,
    383      listing starts from the first block.
    384 
    385    **Response**
    386 
    387    :http:statuscode:`200 OK`:
    388      The body is a JSON array of `BlockEntry` objects.  The array is
    389      empty if the account has no blocks.
    390    :http:statuscode:`400 Bad request`:
    391      The ``limit`` parameter is missing, malformed, given without a
    392      value, or not positive; or ``start_nonce`` is malformed or given
    393      without a value.
    394    :http:statuscode:`402 Payment required`:
    395      The account has expired and requires payment.
    396    :http:statuscode:`404 Not found`:
    397      The ``start_nonce`` block was not found in the linked list.
    398    :http:statuscode:`500 Internal server error`:
    399      A database error occurred.
    400 
    401    .. ts:def:: BlockEntry
    402 
    403       interface BlockEntry {
    404         nonce: BlockUuid;
    405         block_hash: HashCodeString;
    406         prev_nonce?: BlockUuid;
    407         next_nonce?: BlockUuid;
    408         data: string;
    409         upload_sig: EddsaSignatureString;
    410         old_hash: HashCodeString;
    411         refs_hash: HashCodeString;
    412       }
    413 
    414    ``data`` is the encrypted block payload as it was uploaded, and hashes to
    415    ``block_hash``.  ``prev_nonce`` and ``next_nonce`` are absent for the first
    416    and last block of the linked list respectively.  ``upload_sig`` is the
    417    signature stored with the block, and ``old_hash`` / ``refs_hash`` the
    418    remainder of the signed context; the wallet verifies the signature before
    419    applying the block.
    420 
    421 .. http:post:: /backups/${ACCOUNT_KEY}/blocks/${NONCE}
    422 
    423    Upload a new block and append it at the end of the account's linked list.
    424    If a block with the same nonce already exists, the content hash is
    425    compared: if it matches, a ``304 Not modified`` is returned; if it differs,
    426    the client should use ``PUT`` instead.
    427 
    428    The request must include an ``If-None-Match`` header containing the quoted
    429    base32-encoded SHA-512 hash of the encrypted block data.  This hash is used
    430    by the server to detect duplicates, and the server rejects the upload if
    431    the ``data`` in the body does not hash to it.
    432 
    433    **Request**
    434 
    435    :query fresh:
    436      Optional. Force the server to issue a fresh payment order even if a
    437      pending one already exists for this account.
    438    :query pay:
    439      Optional. Any non-empty value (e.g. ``y``) signals that the client
    440      wants to pay before uploading.
    441    :query paying:
    442      Optional. An existing order identifier.  The client is promising
    443      that it is already paying on a related order.  This will cause the
    444      server to delay processing until the respective payment has arrived
    445      (if the operation requires a payment).  Useful if the server
    446      previously returned a ``402 Payment required`` and the client wants
    447      to proceed as soon as the payment went through.
    448 
    449    The request body is a JSON object:
    450 
    451    .. code-block:: typescript
    452 
    453       interface UploadBlockRequest {
    454         upload_sig: EddsaSignatureString;
    455         prev_nonce?: BlockUuid;
    456         next_nonce?: BlockUuid;
    457         data: string;
    458         object_refs?: { [uid: BlobUid]: number };
    459         relink_prev?: { upload_sig: EddsaSignatureString };
    460       }
    461 
    462    ``upload_sig``
    463      EdDSA signature over the block nonce, ``prev_nonce``,
    464      ``next_nonce``, old data hash (for updates, all-zeros for appends),
    465      new data hash and the hash over ``object_refs``, signed with the
    466      account's private key
    467      (``TALER_SIGNATURE_SYNC_BLOCK_UPLOAD``).
    468 
    469    ``prev_nonce``
    470      Nonce of the preceding block in the DLL.
    471      Must be omitted for the first block.
    472 
    473    ``next_nonce``
    474      Must be omitted; inserts into the middle of the linked list are not
    475      supported, so an append never has a succeeding block.
    476 
    477    ``data``
    478      The encrypted block contents (binary, base32-encoded).
    479 
    480    ``object_refs``
    481      Optional object whose keys are blob UIDs and whose values are
    482      16-bit signed integer reference-count deltas.  Any objects
    483      referenced here must have been uploaded *beforehand* via
    484      ``POST /backups/${ACCOUNT_KEY}/objects/${UID}``, and each UID may
    485      appear at most once.  The adjustments are applied in the same
    486      transaction as the block operation: if any of them names an object
    487      the account does not have, or would take a reference count below
    488      zero, the entire request is rejected and nothing is modified.
    489 
    490    ``relink_prev``
    491      Required when ``prev_nonce`` is present.  The new signature of the
    492      block at ``prev_nonce`` (the previous tail), covering its new
    493      ``next`` link after this append.  The server verifies it against
    494      the tail's current state and stores it with the block.
    495 
    496    **Response**
    497 
    498    :http:statuscode:`204 No content`:
    499      The block was stored successfully.
    500    :http:statuscode:`304 Not modified`:
    501      A block with the same nonce and data hash already exists.
    502    :http:statuscode:`400 Bad request`:
    503      Malformed parameters, bad hash, or missing required headers.
    504    :http:statuscode:`402 Payment required`:
    505      The account has expired and requires payment.  The response includes
    506      a ``Taler`` header with a ``taler://pay/...`` URI.
    507    :http:statuscode:`403 Forbidden`:
    508      The signature is invalid or does not match the request.
    509    :http:statuscode:`409 Conflict`:
    510      The request does not fit the state the server holds, and retrying
    511      it unchanged will not help.  Either the write is outdated (the
    512      linked list has been modified by another device since the caller
    513      last fetched it), the nonce is already in use, or ``object_refs``
    514      names an object the account does not have or would take a
    515      reference count below zero.  Nothing was modified.
    516    :http:statuscode:`413 Request entity too large`:
    517      The upload exceeds the server's configured upload limit.
    518    :http:statuscode:`500 Internal server error`:
    519      A database error occurred.
    520 
    521 .. http:put:: /backups/${ACCOUNT_KEY}/blocks/${NONCE}
    522 
    523    Replace an existing block's content in-place.  Semantics are identical to
    524    ``POST`` on the same endpoint, with one addition: the ``If-Match`` header
    525    must contain the quoted base32-encoded SHA-512 hash of the old block data
    526    that is being replaced.  The server rejects the request with ``409
    527    Conflict`` if the old hash, ``prev_nonce`` or ``next_nonce`` do not match
    528    the stored block.
    529 
    530    The ``upload_sig`` must also cover the old data hash (from ``If-Match``) in
    531    addition to the new data hash (from ``If-None-Match``).
    532 
    533    .. note::
    534 
    535       ``PUT`` stands in for ``PATCH``, which the update operation would
    536       otherwise use, until the HTTP server library supports it.
    537 
    538    **Response**
    539 
    540    Same status codes as ``POST``, plus:
    541 
    542    :http:statuscode:`404 Not found`:
    543      The specified block does not exist (cannot update a missing block).
    544 
    545 .. http:delete:: /backups/${ACCOUNT_KEY}/blocks/${NONCE}
    546 
    547    Delete an existing block from the linked list.  The request must include an
    548    ``If-Match`` header containing the quoted base32-encoded SHA-512 hash of
    549    the block data to delete, which the server uses to detect concurrent
    550    modifications.
    551 
    552    **Request**
    553 
    554    The request body is a JSON object:
    555 
    556    .. code-block:: typescript
    557 
    558       interface DeleteBlockRequest {
    559         delete_sig: EddsaSignatureString;
    560         prev_nonce?: BlockUuid;
    561         next_nonce?: BlockUuid;
    562         object_refs?: { [uid: BlobUid]: number };
    563         relink_prev?: { upload_sig: EddsaSignatureString };
    564         relink_next?: { upload_sig: EddsaSignatureString };
    565       }
    566 
    567    ``delete_sig``
    568      EdDSA signature over the block nonce, ``prev_nonce``,
    569      ``next_nonce``, block hash (from ``If-Match``) and the hash over
    570      ``object_refs``, signed with the account's private key
    571      (``TALER_SIGNATURE_SYNC_BLOCK_DELETE``).
    572 
    573    ``prev_nonce``
    574      Nonce of the preceding block in the DLL.
    575      Must be omitted if the block being deleted is the first block.
    576 
    577    ``next_nonce``
    578      Nonce of the succeeding block in the DLL.
    579      Must be omitted if the block being deleted is the last block.
    580 
    581    ``object_refs``
    582      Optional object whose keys are blob UIDs and whose values are
    583      16-bit signed integer reference-count deltas (typically negative,
    584      to decrement the refcount of objects that were referenced by the
    585      deleted block).  The same rules as for block uploads apply: each
    586      UID may appear at most once, and the whole request is rejected if
    587      an adjustment names an unknown object or would take a reference
    588      count below zero.
    589 
    590    ``relink_prev``
    591      Required when ``prev_nonce`` is present.  The new signature of the
    592      block at ``prev_nonce``, covering its new ``next`` link.
    593 
    594    ``relink_next``
    595      Required when ``next_nonce`` is present.  The new signature of the
    596      block at ``next_nonce``, covering its new ``prev`` link.
    597 
    598    **Response**
    599 
    600    :http:statuscode:`204 No content`:
    601      The block was deleted successfully.
    602    :http:statuscode:`400 Bad request`:
    603      Malformed parameters or missing ``If-Match`` header.
    604    :http:statuscode:`402 Payment required`:
    605      The account has expired and requires payment.
    606    :http:statuscode:`403 Forbidden`:
    607      The signature is invalid or does not match the request.
    608    :http:statuscode:`404 Not found`:
    609      The specified block does not exist (or was already deleted).
    610    :http:statuscode:`409 Conflict`:
    611      The ``If-Match`` hash, ``prev_nonce`` or ``next_nonce`` do not
    612      match the stored block (concurrent modification detected), or
    613      ``object_refs`` names an object the account does not have or would
    614      take a reference count below zero.  Nothing was modified.
    615    :http:statuscode:`500 Internal server error`:
    616      A database error occurred.
    617 
    618 Hash-indexed object store
    619 -------------------------
    620 
    621 All static large binary objects (blobs) referenced in a new block generated by
    622 the wallet are required to be uploaded separately to the sync server in
    623 encrypted form before the actual referencing block is uploaded.
    624 
    625 Blobs are stored in a hash-indexed object store with a reference count of
    626 zero, which increases with every referencing block that is uploaded to the
    627 block store. Any blobs with a reference count of zero will be deleted from the
    628 server after a preconfigured expiration period.
    629 
    630 Uploads are keyed by UID and are idempotent: re-uploading a UID that the
    631 account already holds is accepted and changes nothing, so a wallet that is
    632 unsure whether a blob is already present can simply upload it again.  The
    633 stored contents of an existing UID are never replaced.
    634 
    635 Blob format
    636 ~~~~~~~~~~~
    637 
    638 Similar to blocks, each blob consists of 2-byte version number, the 4-byte
    639 data length, the gzipped data, and a padding to the next whole kilobyte. The
    640 blob is then encrypted using a key derived from the wallet's backup encryption
    641 key and the hash of the unencrypted file:
    642 
    643 .. code-block:: text
    644 
    645    key = KDF(32, backup_key, "taler-sync-blob-secret-salt", H(plaintext))
    646    uid = H(key)
    647 
    648 Every blob therefore has its own key. The 64-byte ``uid``, which is the
    649 SHA-512 hash of that key, is what indexes the object in the store and is the
    650 only one of the two the sync server ever learns; the key itself is stored
    651 *inside the blocks* that reference the blob, where it doubles as the reference
    652 to the object that has to be fetched.
    653 
    654 The key is thus all a wallet needs to both locate and decrypt a blob, which is
    655 the only thing a block carries. The `secretbox`_ nonce is consequently derived
    656 from the key as well, as the first 24 bytes of ``H(key)``. Nonce reuse cannot
    657 occur, because distinct plaintexts derive distinct keys.
    658 
    659 Because the key is derived from the plaintext, blobs are content-addressed:
    660 identical contents yield the same key, UID and ciphertext, so an unchanged
    661 blob is only ever uploaded once.
    662 
    663 .. code-block:: text
    664 
    665    +----------------------------+
    666    | version number (2 byte)    |
    667    +----------------------------+
    668    | data length n (4 byte)     |
    669    +----------------------------+
    670    | gzipped data (n byte)      |
    671    +----------------------------+
    672    | padding (to next full KB)  |
    673    +----------------------------+
    674 
    675 Object store API
    676 ~~~~~~~~~~~~~~~~
    677 
    678 Objects are scoped to the account: a UID is only ever visible to the account
    679 that uploaded it.
    680 
    681 .. http:get:: /backups/${ACCOUNT_KEY}/objects/${UID}
    682 
    683    Retrieve an existing blob by its UID.
    684 
    685    **Response**
    686 
    687    :http:statuscode:`200 OK`:
    688      The body is an `ObjectEntry` object.
    689    :http:statuscode:`400 Bad request`:
    690      The ``$UID`` is malformed.
    691    :http:statuscode:`404 Not found`:
    692      The account has no object under that UID.  This is also the answer
    693      for an account that does not exist.
    694    :http:statuscode:`500 Internal server error`:
    695      A database error occurred.
    696 
    697    .. ts:def:: ObjectEntry
    698 
    699       interface ObjectEntry {
    700         uid: BlobUid;
    701         data: string;
    702       }
    703 
    704 .. http:post:: /backups/${ACCOUNT_KEY}/objects/${UID}
    705 
    706    Upload an encrypted blob and store it in the hash-indexed object store.
    707    The ``$UID`` is the object's unique identifier.
    708 
    709    The object is stored with a reference count of zero; it only becomes
    710    referenced once a block naming it in ``object_refs`` is uploaded.  Until
    711    then it is subject to expiry, so blobs should be uploaded shortly before
    712    the block that references them.
    713 
    714    **Request**
    715 
    716    The request body is a JSON object:
    717 
    718    .. code-block:: typescript
    719 
    720       interface UploadObjectRequest {
    721         object_sig: EddsaSignatureString;
    722         data: string;
    723       }
    724 
    725    ``object_sig``
    726      EdDSA signature over the ``$UID`` and the hash of ``data``, signed
    727      with the account's private key
    728      (``TALER_SIGNATURE_SYNC_OBJECT_UPLOAD``).
    729 
    730    ``data``
    731      The encrypted blob contents (binary, base32-encoded).
    732 
    733    **Response**
    734 
    735    :http:statuscode:`204 No content`:
    736      The object was stored.  This is also the answer when the account
    737      already holds an object under that UID, in which case the stored
    738      contents are left as they are.
    739    :http:statuscode:`400 Bad request`:
    740      The ``$UID`` or the request body is malformed.
    741    :http:statuscode:`402 Payment required`:
    742      The account has expired and requires payment.
    743    :http:statuscode:`403 Forbidden`:
    744      The signature is invalid or does not match the request.
    745    :http:statuscode:`413 Request entity too large`:
    746      The upload exceeds the server's configured upload limit.
    747    :http:statuscode:`500 Internal server error`:
    748      A database error occurred.
    749 
    750 .. TODO: synchronization primitive
    751 
    752 Backup schema
    753 -------------
    754 
    755 Local operations on the wallet database are collected into a temporary buffer,
    756 called an “increment set”. Each top-level key in this set holds a list of
    757 insertion operations (“increments”) for a particular database entity
    758 (e.g. exchanges) or event (e.g. payments).
    759 
    760 .. ts:def:: IncrementSet
    761 
    762    interface IncrementSet {
    763      addExchangeIncs?: AddExchangeInc[];
    764      setGlobalExchangeTrustIncs?: SetGlobalExchangeTrustInc[];
    765      addBankAccountIncs?: AddBankAccountInc[];
    766      // ...
    767    }
    768 
    769 When a backup operation is triggered, this buffer is processed into a block
    770 and subsequently emptied. The resulting block gets assigned a random UUID,
    771 appended to the local linked-list, and uploaded to the backup service.
    772 
    773 Since the operations in a given wallet may conflict with operations in the
    774 backup with matching primary keys, a state-based CRDT “merge” strategy was
    775 carefuly devised for every top-level operation type in the block, so that
    776 wallets can deterministically agree on a consistent global state.
    777 
    778 One rule cuts across all of the transaction families: **a transaction only
    779 ever moves towards its end.** The wallets of a group work on the same
    780 transactions at the same time, so an increment that would take a record back
    781 to a state it has already moved past is describing an older view of it, and
    782 only its origin block is recorded.  The terminal states are ranked rather than
    783 simply frozen, so that two wallets which reached *different* ones both settle
    784 on the same one:
    785 
    786 .. code-block:: text
    787 
    788    done  >  failed  >  aborted  >  expired  >  (not terminal)
    789 
    790 Preferring ``done`` is deterministic, which is what convergence needs, and it
    791 is also the truthful answer: a transaction that finished actually moved the
    792 money.  Without the rule, a wallet that completed a withdrawal would pull in
    793 the abort another device had issued against the copy it restored, and end up
    794 showing an abandoned transaction while holding the coins it produced.
    795 
    796 Add or update an exchange
    797 ~~~~~~~~~~~~~~~~~~~~~~~~~
    798 
    799 User accepts ToS for a new or existing exchange.
    800 
    801 Exchanges without an accepted ToS are not included in the backup.
    802 
    803 .. ts:def:: AddExchangeInc
    804 
    805    interface AddExchangeInc {
    806      type: "add-exchange";
    807      exchangeBaseUrl: string;
    808      tosAcceptedEtag: string;
    809      tosAcceptedEtagTimestamp: Timestamp;
    810    }
    811 
    812 * **Primary key:** ``[exchangeBaseUrl]``
    813 * **Deletion groups:** ``[exchanges]``
    814 
    815 Merge strategy
    816 ++++++++++++++
    817 
    818 Favor the operation with the largest ``tosAcceptedEtagTimestamp``. If two
    819 timestamps are equal, favor the operation with the largest ``tosAcceptedEtag``
    820 in lexicographical order.
    821 
    822 Set exchange to global trust
    823 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    824 
    825 User sets an exchange to global trust.
    826 
    827 .. ts:def:: SetGlobalExchangeTrustInc
    828 
    829    interface SetGlobalExchangeTrustInc {
    830      type: "set-global-exchange-trust";
    831      exchangeBaseUrl: string;
    832      exchangeMasterPub: EddsaPublicKey;
    833    }
    834 
    835 * **Primary key:** ``[exchangeBaseUrl, exchangeMasterPub]``
    836 * **Deletion groups:** ``[global-exchange-trust]``
    837 
    838 Merge strategy
    839 ++++++++++++++
    840 
    841 No merge is required.
    842 
    843 Add or update a bank account
    844 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    845 
    846 User adds (or updates) a known bank account.
    847 
    848 .. ts:def:: AddBankAccountInc
    849 
    850    interface AddBankAccountInc {
    851      type: "add-bank-account";
    852      bankAccountId: string;
    853      paytoUri: string;
    854      label: string;
    855    }
    856 
    857 * **Primary key:** ``[bankAccountId]``
    858 * **Deletion groups:** ``[bank-accounts]``
    859 
    860 Merge strategy
    861 ++++++++++++++
    862 
    863 Last write wins.
    864 
    865 Set Donau info
    866 ~~~~~~~~~~~~~~
    867 
    868 User sets info for tax-deductible donations.
    869 
    870 .. ts:def:: SetDonauInfoInc
    871 
    872    interface SetDonauInfoInc {
    873      type: "set-donau-info";
    874      donauBaseUrl: string;
    875      taxPayerId: string;
    876    }
    877 
    878 * **Primary key:** ``[info]``
    879 * **Deletion groups:** ``[donau-info]``
    880 
    881 Merge strategy
    882 ++++++++++++++
    883 
    884 Last write wins.
    885 
    886 Add a denomination
    887 ~~~~~~~~~~~~~~~~~~
    888 
    889 A denomination is stored in the wallet.
    890 
    891 .. ts:def:: AddDenominationInc
    892 
    893    interface AddDenominationInc {
    894      type: "add-denomination";
    895      denomPub: DenominationPubKey;
    896      value: AmountString;
    897      fees: DenomFees;
    898      stampStart: TalerProtocolTimestamp;
    899      stampExpireWithdraw: TalerProtocolTimestamp;
    900      stampExpireLegal: TalerProtocolTimestamp;
    901      stampExpireDeposit: TalerProtocolTimestamp;
    902      masterSig: EddsaSignature;
    903      exchangeBaseUrl: string;
    904      exchangeMasterPub: EddsaPublicKey;
    905    }
    906 
    907 * **Primary key:** ``[exchangeBaseUrl, denomPub]``
    908 * **Deletion groups:** ``[denominations]``
    909 
    910 Merge strategy
    911 ++++++++++++++
    912 
    913 No merge is required, a denomination is expected to always remain constant, so
    914 later additions of the same denomination can be safely discarded.
    915 
    916 Add a coin
    917 ~~~~~~~~~~
    918 
    919 A coin comes into the wallet (withdrawn or refreshed) and is signed by the
    920 exchange.
    921 
    922 The wallet database stores per-coin key material, so the increment carries the
    923 coin **as it stands** -- key, blinding key, signature and status -- rather
    924 than deriving it from a seed as earlier designs did.  The wallet records an
    925 ``add-coin`` when the coin is created and a ``spend-coin`` when it is spent;
    926 the full collection pass emits the ``add-coin`` form for any coin the backup
    927 has never seen, whatever state it is in.  The ``spend-coin`` section is
    928 applied after the ``add-coin`` section, so a coin that was spent before a
    929 cycle ran restores in its spent state.
    930 
    931 Restoring the coin also recomputes the wallet's *coin availability* rows (the
    932 counts the balance reads) from the restored coins, so a restored wallet shows
    933 the same balance as the wallet that made the backup.  The counts are always
    934 derived and never carried, which is what makes the restore idempotent; only a
    935 coin that is spendable (status ``fresh``) counts, matching what the wallet's
    936 own bookkeeping does with a suspended one.
    937 
    938 For the two balances to agree, *every* change to whether a coin counts has to
    939 reach the other wallets, not only spending: a coin melted into a refresh,
    940 recouped from a revoked denomination, written off with its denomination, or
    941 suspended by the user is reported with a ``spend-coin`` increment carrying its
    942 new status.  The section is the coin's terminal update, whatever brought it
    943 about.  A change that is not reported is the one way the two devices can end
    944 up disagreeing about how much money the user has, since a coin that is already
    945 backed up is never offered again by the full collection pass.
    946 
    947 The reserves (and with them the ability to recoup a restored coin) are backed
    948 up by the ``add-reserve`` family, and the withdrawal family
    949 (``withdrawal-start`` / ``withdrawal-abort`` / ``withdrawal-done`` /
    950 ``withdrawal-fail``, referencing the reserve by ``[exchangeBaseUrl,
    951 reservePub]``, and carrying the ``wgInfo`` with the ``taler://withdraw`` URI
    952 that identifies the bank's operation) restores the withdrawal transactions
    953 themselves and lets a restored wallet continue a pending one -- the bank's
    954 operation is keyed by that URI, and the reserve key pair and the coin seed are
    955 in the backup too; only an expired bank operation cannot be resumed.  A
    956 refreshed coin's melt is backed up by the refresh family below, so a restored
    957 coin can be recouped-refreshed as well as recouped (see the recoup discussion
    958 under "Add a reserve").
    959 
    960 ``exchangeWithdrawValues`` carries the blinding values the exchange
    961 contributed to the withdraw, which a recoup has to replay.  For an RSA coin
    962 they are the constant ``{"cipher": "RSA"}``; for a Clause-Schnorr coin they
    963 are the R-values, which nothing can re-derive, so they have to travel in the
    964 increment.  The field is optional because it was added after the increment was
    965 first released: a coin from a wallet that predates it is treated as RSA.
    966 
    967 .. ts:def:: AddCoinInc
    968 
    969    interface AddCoinInc {
    970      type: "add-coin";
    971      coinSource: CoinSource;
    972      sourceTransactionId?: string;
    973      coinPub: string;
    974      coinPriv: string;
    975      denomPubHash: string;
    976      denomSig: UnblindedDenominationSignature;
    977      exchangeBaseUrl: string;
    978      exchangeMasterPub: string;
    979      blindingKey: string;
    980      coinEvHash: string;
    981      status: CoinStatus;
    982      visible?: number;
    983      maxAge: number;
    984      ageCommitmentProof?: AgeCommitmentProof;
    985      exchangeWithdrawValues?: ExchangeWithdrawValue;
    986    }
    987 
    988 .. ts:def:: CoinSource
    989 
    990    type CoinSource =
    991      | WithdrawalCoinSource
    992      | RefreshCoinSource;
    993 
    994 .. ts:def:: WithdrawalCoinSource
    995 
    996    interface WithdrawalCoinSource {
    997      type: "withdrawal";
    998      withdrawalGroupId: string;
    999      coinNumber: number;
   1000      reservePub: string;
   1001    }
   1002 
   1003 .. ts:def:: RefreshCoinSource
   1004 
   1005    interface RefreshCoinSource {
   1006      type: "refresh";
   1007      refreshGroupId: string;
   1008      oldCoinPub: string;
   1009    }
   1010 
   1011 * **Primary key:** ``[coinPub]``
   1012 * **Deletion groups:** ``[coins]``
   1013 
   1014 Merge strategy
   1015 ++++++++++++++
   1016 
   1017 Last write wins: a coin is unique and its parameters never change, so the
   1018 latest copy wins.
   1019 
   1020 Spend a coin
   1021 ~~~~~~~~~~~~
   1022 
   1023 A signed coin is spent by the user.
   1024 
   1025 .. ts:def:: SpendCoinInc
   1026 
   1027    interface SpendCoinInc {
   1028      type: "spend-coin";
   1029      coinSource: CoinSource;
   1030      sourceTransactionId?: string;
   1031      coinPub: string;
   1032      coinPriv: string;
   1033      denomPubHash: string;
   1034      denomSig: UnblindedDenominationSignature;
   1035      exchangeBaseUrl: string;
   1036      exchangeMasterPub: string;
   1037      blindingKey: string;
   1038      coinEvHash: string;
   1039      status: CoinStatus;
   1040      visible?: number;
   1041      maxAge: number;
   1042      ageCommitmentProof?: AgeCommitmentProof;
   1043      exchangeWithdrawValues?: ExchangeWithdrawValue;
   1044    }
   1045 
   1046 * **Primary key:** ``[coinPub]``
   1047 * **Deletion groups:** ``[coins]``
   1048 
   1049 Add a token
   1050 ~~~~~~~~~~~
   1051 
   1052 A token is generated by the wallet but not yet signed by the merchant (the
   1053 wallet database calls this a *slate*).
   1054 
   1055 Like coins, tokens were originally designed as seed-derived: the increment
   1056 carried ``[secretSeed, choiceIndex, outputIndex]`` and the wallet re-derived
   1057 the key pair from it.  The wallet database stores per-token key material
   1058 instead, so the increments carry the token as it stands, and the token's *use*
   1059 public key is the primary key of the family.  The three increments share one
   1060 body, ``TokenIncBase``:
   1061 
   1062 .. ts:def:: TokenIncBase
   1063 
   1064    interface TokenIncBase {
   1065      // Purchase the token belongs to, and the position within its
   1066      // contract that produced it.
   1067      purchaseId: string;
   1068      transactionId?: string;
   1069      choiceIndex?: number;
   1070      outputIndex?: number;
   1071      repeatIndex?: number;
   1072 
   1073      merchantBaseUrl: string;
   1074      kind: MerchantContractTokenKind;
   1075      slug: string;
   1076      name: string;
   1077      description: string;
   1078      descriptionI18n?: InternationalizedString;
   1079      extraData: MerchantContractTokenDetails;
   1080 
   1081      tokenIssuePub: TokenIssuePublicKey;
   1082      tokenIssuePubHash: string;
   1083      tokenFamilyHash?: string;
   1084      validAfter: TalerProtocolTimestamp;
   1085      validBefore: TalerProtocolTimestamp;
   1086 
   1087      // The key material the wallet holds for this token.  Nothing can
   1088      // reconstruct it, so it travels in the increment.
   1089      tokenUsePub: string;
   1090      tokenUsePriv: string;
   1091      tokenUseSig?: TokenUseSig;
   1092      tokenEv: TokenEnvelope;
   1093      tokenEvHash: string;
   1094      blindingKey: string;
   1095    }
   1096 
   1097 .. ts:def:: AddTokenInc
   1098 
   1099    interface AddTokenInc extends TokenIncBase {
   1100      type: "add-token";
   1101    }
   1102 
   1103 * **Primary key:** ``[tokenUsePub]``
   1104 * **Deletion groups:** ``[tokens]``
   1105 
   1106 Merge strategy
   1107 ++++++++++++++
   1108 
   1109 No merge is required, new tokens are unique.
   1110 
   1111 Sign a token
   1112 ~~~~~~~~~~~~
   1113 
   1114 A token is signed by the merchant.  Applying this increment also removes the
   1115 slate the token was issued from, the same way the wallet's own issuance flow
   1116 does.
   1117 
   1118 .. ts:def:: SignTokenInc
   1119 
   1120    interface SignTokenInc extends TokenIncBase {
   1121      type: "sign-token";
   1122      tokenIssueSig: UnblindedDenominationSignature;
   1123    }
   1124 
   1125 * **Primary key:** ``[tokenUsePub]``
   1126 * **Deletion groups:** ``[tokens]``
   1127 
   1128 Merge strategy
   1129 ++++++++++++++
   1130 
   1131 No merge is required, only one signature for a given token can be issued by
   1132 the merchant, further attempts to sign it will fail.
   1133 
   1134 Spend a token
   1135 ~~~~~~~~~~~~~
   1136 
   1137 A signed token is spent by the user.  Only the fields the spend changes
   1138 travel; the increment updates a token that is already there and is skipped
   1139 when it is not.
   1140 
   1141 .. ts:def:: SpendTokenInc
   1142 
   1143    interface SpendTokenInc {
   1144      type: "spend-token";
   1145      tokenUsePub: string;
   1146      transactionId?: string;
   1147      tokenUseSig?: TokenUseSig;
   1148    }
   1149 
   1150 * **Primary key:** ``[tokenUsePub]``
   1151 * **Deletion groups:** ``[tokens]``
   1152 
   1153 Merge strategy
   1154 ++++++++++++++
   1155 
   1156 No merge is required, each token can only be spent once, further attempts at
   1157 spending the token will fail.
   1158 
   1159 Start a withdrawal
   1160 ~~~~~~~~~~~~~~~~~~
   1161 
   1162 User initiates a withdrawal.
   1163 
   1164 The increment references the reserve by ``[exchangeBaseUrl, reservePub]`` (see
   1165 the "Add a reserve" section): the restored wallet takes the reserve's key pair
   1166 from the reserve record.  It also carries the ``wgInfo`` -- for a
   1167 bank-integrated withdrawal, the ``taler://withdraw`` URI that identifies the
   1168 bank's withdrawal operation.  That URI, the reserve key pair and the coin seed
   1169 (all in the backup) are everything a restored wallet needs to continue a
   1170 withdrawal that was still pending on the other device; the only thing that
   1171 cannot be resumed is a bank operation the bank has already expired or deleted.
   1172 
   1173 .. ts:def:: WithdrawalStartInc
   1174 
   1175    interface WithdrawalStartInc {
   1176      type: "withdrawal-start";
   1177      withdrawalGroupId: string;
   1178      exchangeBaseUrl: string;
   1179      reservePub: EddsaPublicKey;
   1180      secretSeed: string;
   1181      timestampStart: TalerPreciseTimestamp;
   1182      restrictAge?: number;
   1183      instructedAmount?: AmountString;
   1184      wgInfo: WgInfo;
   1185    }
   1186 
   1187 * **Primary key:** ``[withdrawalGroupId]``
   1188 * **Deletion groups:** ``[withdrawals]``
   1189 
   1190 Merge strategy
   1191 ++++++++++++++
   1192 
   1193 No merge is required, all withdrawals are independent from each other.
   1194 
   1195 Abort a withdrawal
   1196 ~~~~~~~~~~~~~~~~~~
   1197 
   1198 User aborts a withdrawal.
   1199 
   1200 .. ts:def:: WithdrawalAbortInc
   1201 
   1202    interface WithdrawalAbortInc {
   1203      type: "withdrawal-abort";
   1204      withdrawalGroupId: string;
   1205      abortReason?: TalerErrorDetail;
   1206    }
   1207 
   1208 * **Primary key:** ``[withdrawalGroupId]``
   1209 * **Deletion groups:** ``[withdrawals]``
   1210 
   1211 Merge strategy
   1212 ++++++++++++++
   1213 
   1214 Store all ``abortReason`` in the database.
   1215 
   1216 Withdrawal done
   1217 ~~~~~~~~~~~~~~~
   1218 
   1219 A withdrawal started by the user completes successfully.
   1220 
   1221 .. ts:def:: WithdrawalDoneInc
   1222 
   1223    interface WithdrawalDoneInc {
   1224      type: "withdrawal-done";
   1225      withdrawalGroupId: string;
   1226      timestampFinish: TalerPreciseTimestamp;
   1227      rawWithdrawalAmount: AmountString;
   1228      effectiveWithdrawalAmount: AmountString;
   1229    }
   1230 
   1231 * **Primary key:** ``[withdrawalGroupId]``
   1232 * **Deletion groups:** ``[withdrawals]``
   1233 
   1234 Merge strategy
   1235 ++++++++++++++
   1236 
   1237 No merge is required, a withdrawal can only succeed once.
   1238 
   1239 Withdrawal failed
   1240 ~~~~~~~~~~~~~~~~~
   1241 
   1242 A withdrawal started by the user fails.
   1243 
   1244 .. ts:def:: WithdrawalFailInc
   1245 
   1246    interface WithdrawalFailInc {
   1247      type: "withdrawal-fail";
   1248      withdrawalGroupId: string;
   1249      failReason: TalerErrorDetail;
   1250    }
   1251 
   1252 * **Primary key:** ``[withdrawalGroupId]``
   1253 * **Deletion groups:** ``[withdrawals]``
   1254 
   1255 Merge strategy
   1256 ++++++++++++++
   1257 
   1258 Store all ``failReason`` in the database.
   1259 
   1260 .. TODO: withdrawal (soft) deletion as increment?
   1261    (can't be easily deleted because of coin references)
   1262 
   1263 Set the reserve seed
   1264 ~~~~~~~~~~~~~~~~~~~~
   1265 
   1266 The wallet derives every reserve key pair from a single wallet-level seed (32
   1267 random bytes), so that the backup carries no per-reserve key material: the
   1268 private key of reserve ``i`` is re-derived as
   1269 
   1270 .. code-block:: text
   1271 
   1272    reservePriv_i = KDF(32, reserveSeed, "taler-reserve-key-salt", i)
   1273 
   1274 and the public key from the private one (``eddsa_get_public``).  The seed
   1275 itself is wallet state and travels in the backup like the wallet root key;
   1276 this increment is what the backup carries it as.  It is created lazily at the
   1277 first reserve created after this feature ships, so wallets that predate it do
   1278 not grow a seed until they create their next reserve.  Reserves created before
   1279 the seed existed keep their random key pairs and are backed up with the
   1280 ``reservePriv`` fallback of ``add-reserve`` below.
   1281 
   1282 .. ts:def:: SetReserveSeedInc
   1283 
   1284    interface SetReserveSeedInc {
   1285      type: "set-reserve-seed";
   1286      seed: string;
   1287    }
   1288 
   1289 * **Primary key:** ``[]`` (a singleton, like ``set-donau-info``)
   1290 * **Deletion groups:** ``[reserve-seed]``
   1291 
   1292 Merge strategy
   1293 ++++++++++++++
   1294 
   1295 Last write wins.
   1296 
   1297 The ``set-reserve-seed`` section of an increment set is applied before the
   1298 ``add-reserve`` section, so that a wallet deriving a reserve key pair on
   1299 restore already has the seed.
   1300 
   1301 Add a reserve
   1302 ~~~~~~~~~~~~~
   1303 
   1304 A reserve is created by the wallet for every withdrawal and for the merge
   1305 capability of P2P payments, and its key pair lives in the wallet's
   1306 ``reserves`` object store (see the ``WalletReserve`` record in ``db.ts``).
   1307 The increment carries the record's identity -- the exchange and the reserve's
   1308 derivation index -- and, for the reserves that predate the seed, the private
   1309 key.
   1310 
   1311 .. ts:def:: AddReserveInc
   1312 
   1313    interface AddReserveInc {
   1314      type: "add-reserve";
   1315      exchangeBaseUrl: string;
   1316      reserveIndex: number;
   1317      // Only for reserves created before the reserve seed existed, whose
   1318      // keys are random and cannot be re-derived.
   1319      reservePriv?: EddsaPrivateKey;
   1320    }
   1321 
   1322 * **Primary key:** ``[exchangeBaseUrl, reserveIndex]``
   1323 * **Deletion groups:** ``[reserves]``
   1324 
   1325 Merge strategy
   1326 ++++++++++++++
   1327 
   1328 Last write wins: the identity of a reserve never changes, and a re-recorded
   1329 increment (e.g. by the full collection pass) carries the same index and the
   1330 same key material.
   1331 
   1332 The public key of the reserve is *not* carried: it is derived from the private
   1333 key on restore (``eddsa_get_public``), whether the private key was re-derived
   1334 from the seed or restored from ``reservePriv``.  The restored record therefore
   1335 has the same ``reservePub`` as the wallet that created the reserve, which is
   1336 what the other increments reference it by (see below).  The ``WalletReserve``
   1337 record gains ``exchangeBaseUrl``, ``reserveIndex`` and the
   1338 ``reserveSeedDerived`` marker (which decides whether the full collection pass
   1339 emits the index-only form or the index-plus-private-key form); the exchange
   1340 base URL is required by the increment and was missing from the record (see the
   1341 ``FIXME: Should reference exchange.`` comment in ``db.ts`` and the redundant
   1342 ``exchangeBaseUrl`` of ``WithdrawalGroupRecord``).
   1343 
   1344 The remaining fields of ``WalletReserve`` (``status``, the KYC thresholds,
   1345 ``kycAccessToken``, ``amlReview``) are all derivable by querying the exchange
   1346 and are deliberately not backed up, so that a restored wallet re-derives them
   1347 instead of trusting stale state.
   1348 
   1349 Recoup
   1350 ++++++
   1351 
   1352 The reserve increment is what keeps recoup working on a restored wallet.  The
   1353 recoup request itself is signed by the *coin*: the coin record (``add-coin``)
   1354 carries the coin private key, the blinding key and the denomination signature
   1355 the request needs, and the request names the reserve only by its public key,
   1356 which the coin source carries.  After the exchange confirms the recoup, the
   1357 wallet queries the reserve's balance and withdraws it back into coins; that
   1358 re-withdrawal needs the reserve *private* key, which is exactly what
   1359 ``add-reserve`` restores.  The recoup of a refreshed coin (``recoup-refresh``)
   1360 likewise needs only the coin records -- the refreshed coin plus the old coin
   1361 the refresh source names -- so no refresh-group data is involved.
   1362 
   1363 The upcoming batch recoup protocol (``vRECOUP``, see ``api-exchange.rst``)
   1364 adds, per coin, the Clause-Schnorr blinding data (``cs_session_nonce`` and the
   1365 ``cs_r_pubs`` of the exchange's ``/blinding-prepare``) for post-quantum
   1366 denominations.  The wallet does not store that data anywhere yet; when it
   1367 does, the ``add-coin`` increment must carry it (as optional fields).  That is
   1368 a coin-family extension; the reserve side of a post-quantum recoup stays as
   1369 described above.
   1370 
   1371 Why the schema matters to the other increment types
   1372 ++++++++++++++++++++++++++++++++++++++++++++++++++++
   1373 
   1374 The ``reserves`` store is referenced, directly or through its row id, by the
   1375 withdrawal groups (``reservePub``/``reservePriv``), the coin sources
   1376 (``WithdrawCoinSource.reservePub``, used for recouping), the exchange entries
   1377 (``currentMergeReserveRowId``) and the peer-pull-credit records
   1378 (``mergeReserveRowId``):
   1379 
   1380 * ``withdrawal-start`` is the most obvious case: the wallet's
   1381   ``WithdrawalGroupRecord`` embeds the reserve key pair and the exchange base
   1382   URL.  With ``add-reserve``, a ``withdrawal-start`` increment can reference
   1383   the reserve by ``[exchangeBaseUrl, reservePub]`` instead of carrying the key
   1384   pair, avoiding duplication.
   1385 * ``add-coin`` / ``spend-coin`` reference the reserve through the withdrawal
   1386   coin source's ``reservePub``; the restored reserve record is what makes the
   1387   restored coin recoupable (see above).
   1388 * The exchange entries and the peer-pull-credit records reference the merge
   1389   reserve by a *row id* into the ``reserves`` store, which is not portable
   1390   across wallets.  The ``add-exchange`` increment does not carry the
   1391   ``currentMergeReserveRowId`` pointer, so a restored exchange entry starts
   1392   without one; the merge reserve remains findable by its public key, and
   1393   re-linking the pointer on restore is a follow-up.
   1394 
   1395 Every increment family in this document is implemented; see the "Definition of
   1396 done" section for what remains.
   1397 
   1398 Start a deposit
   1399 ~~~~~~~~~~~~~~~
   1400 
   1401 .. ts:def:: DepositStartInc
   1402 
   1403    interface DepositStartInc {
   1404      type: "deposit-start";
   1405      depositGroupId: string;
   1406      currency: string;
   1407      amount: AmountString;
   1408      wireTransferDeadline: TalerProtocolTimestamp;
   1409      merchantPub: EddsaPublicKey;
   1410      merchantPriv: EddsaPrivateKey;
   1411      noncePub: EddsaPublicKey;
   1412      noncePriv: EddsaPrivateKey;
   1413      wire: {payto_uri: string, salt: string};
   1414      contractTermsHash: HashCode; // blob
   1415      totalPayCost: AmountString;
   1416      timestampCreated: TalerPreciseTimestamp;
   1417      infoPerExchange: {[exchangeBaseUrl: string]: DepositInfoPerExchange};
   1418    }
   1419 
   1420 * **Primary key:** ``[depositGroupId]``
   1421 * **Deletion groups:** ``[deposits]``
   1422 
   1423 Merge strategy
   1424 ++++++++++++++
   1425 
   1426 No merge is required, all deposits are independent from each other.
   1427 
   1428 Abort a deposit
   1429 ~~~~~~~~~~~~~~~
   1430 
   1431 User aborts a deposit.
   1432 
   1433 .. ts:def:: DepositAbortInc
   1434 
   1435    interface DepositAbortInc {
   1436      type: "deposit-abort";
   1437      depositGroupId: string;
   1438      abortReason?: TalerErrorDetail;
   1439    }
   1440 
   1441 * **Primary key:** ``[depositGroupId]``
   1442 * **Deletion groups:** ``[deposits]``
   1443 
   1444 Merge strategy
   1445 ++++++++++++++
   1446 
   1447 Store all ``abortReason`` in the database.
   1448 
   1449 Deposit done
   1450 ~~~~~~~~~~~~
   1451 
   1452 A deposit started by the user completes successfully.
   1453 
   1454 .. ts:def:: DepositDoneInc
   1455 
   1456    interface DepositDoneInc {
   1457      type: "deposit-done";
   1458      depositGroupId: string;
   1459      timestampFinished: TalerPreciseTimestamp;
   1460    }
   1461 
   1462 * **Primary key:** ``[depositGroupId]``
   1463 * **Deletion groups:** ``[deposits]``
   1464 
   1465 Merge strategy
   1466 ++++++++++++++
   1467 
   1468 No merge required, a deposit can only succeed once.
   1469 
   1470 Deposit fail
   1471 ~~~~~~~~~~~~
   1472 
   1473 A deposit started by the user fails.
   1474 
   1475 .. ts:def:: DepositFailInc
   1476 
   1477    interface DepositFailInc {
   1478      type: "deposit-fail";
   1479      depositGroupId: string;
   1480      failReason: TalerErrorDetail;
   1481    }
   1482 
   1483 * **Primary key:** ``[depositGroupId]``
   1484 * **Deletion groups:** ``[deposits]``
   1485 
   1486 Merge strategy
   1487 ++++++++++++++
   1488 
   1489 Store all ``failReason`` in the database.
   1490 
   1491 Start a merchant payment
   1492 ~~~~~~~~~~~~~~~~~~~~~~~~
   1493 
   1494 User initiates a payment to a merchant.
   1495 
   1496    .. ts:def:: PaymentStartInc
   1497 
   1498       interface PaymentStartInc {
   1499         type: "payment-start";
   1500         proposalId: string;
   1501         // Not in the original design, but needed to reconstruct the
   1502         // `taler://pay/...' URI and re-download the proposal on restore:
   1503         merchantBaseUrl: string;
   1504         orderId: string;
   1505         claimToken?: string;
   1506         downloadSessionId?: string;
   1507         repurchaseProposalId?: string;
   1508         noncePub: EddsaPublicKey;
   1509         noncePriv: EddsaPrivateKey;
   1510         secretSeed: string;
   1511         exchanges?: string[];
   1512         // Hash of the contract terms (a blob).  Unknown until the
   1513         // proposal has been downloaded.
   1514         contractTermsHash?: string;
   1515         timestamp: TalerPreciseTimestamp;
   1516 
   1517         // Donau
   1518         donauOutputIndex?: number;
   1519         donauBaseUrl?: string;
   1520         donauAmount?: AmountString;
   1521         donauTaxIdHash?: string;
   1522         donauTaxIdSalt?: string;
   1523         donauTaxId?: string;
   1524         donauYear?: number;
   1525       }
   1526 
   1527 * **Primary key:** ``[proposalId]``
   1528 * **Deletion groups:** ``[payments]``
   1529 
   1530 Merge strategy
   1531 ++++++++++++++
   1532 
   1533 No merge is required, all payments are independent from each other.
   1534 
   1535 Confirm a merchant payment
   1536 ~~~~~~~~~~~~~~~~~~~~~~~~~~
   1537 
   1538 User confirms a payment to a merchant.
   1539 
   1540 .. ts:def:: PaymentConfirmInc
   1541 
   1542    interface PaymentConfirmInc {
   1543      type: "payment-confirm";
   1544      proposalId: string;
   1545      choiceIndex?: number;
   1546      timestampAccept: TalerPreciseTimestamp;
   1547    }
   1548 
   1549 * **Primary key:** ``[proposalId]``
   1550 * **Deletion groups:** ``[payments]``
   1551 
   1552 Merge strategy
   1553 ++++++++++++++
   1554 
   1555 No merge is required, a payment can only succeed once.
   1556 
   1557 Abort a merchant payment
   1558 ~~~~~~~~~~~~~~~~~~~~~~~~
   1559 
   1560 User aborts a payment to a merchant.
   1561 
   1562 .. ts:def:: PaymentAbortInc
   1563 
   1564    interface PaymentAbortInc {
   1565      type: "payment-abort";
   1566      proposalId: string;
   1567      abortReason?: TalerErrorDetail;
   1568    }
   1569 
   1570 * **Primary key:** ``[proposalId]``
   1571 * **Deletion groups:** ``[payments]``
   1572 
   1573 Merge strategy
   1574 ++++++++++++++
   1575 
   1576 Store all ``abortReason`` in the database.
   1577 
   1578 Merchant purchase done
   1579 ~~~~~~~~~~~~~~~~~~~~~~
   1580 
   1581 A payment started by the user completes successfully.
   1582 
   1583 .. ts:def:: PaymentDoneInc
   1584 
   1585    interface PaymentDoneInc {
   1586      type: "payment-done";
   1587      proposalId: string;
   1588    }
   1589 
   1590 * **Primary key:** ``[proposalId]``
   1591 * **Deletion groups:** ``[payments]``
   1592 
   1593 Merchant purchase fail
   1594 ~~~~~~~~~~~~~~~~~~~~~~
   1595 
   1596 A payment started by the user fails.
   1597 
   1598 .. ts:def:: PaymentFailInc
   1599 
   1600    interface PaymentFailInc {
   1601      type: "payment-fail";
   1602      proposalId: string;
   1603      failReason: TalerErrorDetail;
   1604    }
   1605 
   1606 * **Primary key:** ``[proposalId]``
   1607 * **Deletion groups:** ``[payments]``
   1608 
   1609 Merge strategy
   1610 ++++++++++++++
   1611 
   1612 Store all ``failReason`` in the database.
   1613 
   1614 Start peer-push-credit
   1615 ~~~~~~~~~~~~~~~~~~~~~~
   1616 
   1617 User receives an incoming push payment.
   1618 
   1619 .. ts:def:: PeerPushCreditStartInc
   1620 
   1621    interface PeerPushCreditStartInc {
   1622      type: "peer-push-credit-start";
   1623      peerPushCreditId: string;
   1624      exchangeBaseUrl: string;
   1625      pursePub: EddsaPublicKey;
   1626      mergePriv: EddsaPrivateKey;
   1627      contractPriv: EddsaPrivateKey;
   1628      timestamp: TalerPreciseTimestamp;
   1629      estimatedAmountEffective: AmountString;
   1630      contractTermsHash: HashCode; // blob
   1631      currency: string;
   1632    }
   1633 
   1634 * **Primary key:** ``[peerPushCreditId]``
   1635 * **Deletion groups:** ``[peer-push-credit]``
   1636 
   1637 Merge strategy
   1638 ++++++++++++++
   1639 
   1640 Last write wins, since the parameters of a peer-push-credit transaction are
   1641 expected to always remain constant. However, ``peerPushCreditId`` must be
   1642 derived from the ``exchangeBaseUrl`` and ``pursePub``.
   1643 
   1644 Abort peer-push-credit
   1645 ~~~~~~~~~~~~~~~~~~~~~~
   1646 
   1647 User aborts an incoming push payment.
   1648 
   1649 .. ts:def:: PeerPushCreditAbortInc
   1650 
   1651    interface PeerPushCreditAbortInc {
   1652      type: "peer-push-credit-abort";
   1653      peerPushCreditId: string;
   1654      abortReason?: TalerErrorDetail;
   1655    }
   1656 
   1657 * **Primary key:** ``[peerPushCreditId]``
   1658 * **Deletion groups:** ``[peer-push-credit]``
   1659 
   1660 Merge strategy
   1661 ++++++++++++++
   1662 
   1663 Store all ``abortReason`` in the database.
   1664 
   1665 Peer-push-credit done
   1666 ~~~~~~~~~~~~~~~~~~~~~
   1667 
   1668 An incoming push payment received by the user completes successfully.
   1669 
   1670 .. ts:def:: PeerPushCreditDoneInc
   1671 
   1672    interface PeerPushCreditDoneInc {
   1673      type: "peer-push-credit-done";
   1674      peerPushCreditId: string;
   1675    }
   1676 
   1677 * **Primary key:** ``[peerPushCreditId]``
   1678 * **Deletion groups:** ``[peer-push-credit]``
   1679 
   1680 Merge strategy
   1681 ++++++++++++++
   1682 
   1683 No merge is required, a peer-push-credit payment can only succeed once.
   1684 
   1685 Peer-push-credit fail
   1686 ~~~~~~~~~~~~~~~~~~~~~
   1687 
   1688 An incoming push payment received by the user fails.
   1689 
   1690 .. ts:def:: PeerPushCreditFailInc
   1691 
   1692    interface PeerPushCreditFailInc {
   1693      type: "peer-push-credit-fail";
   1694      peerPushCreditId: string;
   1695      failReason: TalerErrorDetail;
   1696    }
   1697 
   1698 * **Primary key:** ``[peerPushCreditId]``
   1699 * **Deletion groups:** ``[peer-push-credit]``
   1700 
   1701 Merge strategy
   1702 ++++++++++++++
   1703 
   1704 Store all ``failReason`` in the database.
   1705 
   1706 Start peer-push-debit
   1707 ~~~~~~~~~~~~~~~~~~~~~
   1708 
   1709 User initiates an outgoing push payment.
   1710 
   1711 .. ts:def:: PeerPushDebitStartInc
   1712 
   1713    interface PeerPushDebitStartInc {
   1714      type: "peer-push-debit-start";
   1715      exchangeBaseUrl: string;
   1716      instructedAmount: AmountString;
   1717      effectiveAmount: AmountString;
   1718      contractTermsHash: HashCode; // blob
   1719      pursePub: EddsaPublicKey;
   1720      pursePriv: EddsaPrivateKey;
   1721      mergePub: EddsaPublicKey;
   1722      mergePriv: EddsaPrivateKey;
   1723      contractPub: EddsaPublicKey;
   1724      contractPriv: EddsaPrivateKey;
   1725      contractEncNonce: string;
   1726      purseExpiration: TalerProtocolTimestamp;
   1727      timestampCreated: TalerPreciseTimestamp;
   1728    }
   1729 
   1730 * **Primary key:** ``[pursePub]``
   1731 * **Deletion groups:** ``[peer-push-debit]``
   1732 
   1733 Merge strategy
   1734 ++++++++++++++
   1735 
   1736 No merge is required, all peer-push-debit payments are independent from each
   1737 other.
   1738 
   1739 Abort peer-push-debit
   1740 ~~~~~~~~~~~~~~~~~~~~~
   1741 
   1742 User aborts an outgoing push payment.
   1743 
   1744 .. ts:def:: PeerPushDebitAbortInc
   1745 
   1746    interface PeerPushDebitAbortInc {
   1747      type: "peer-push-debit-abort";
   1748      pursePub: EddsaPublicKey;
   1749      abortReason?: TalerErrorDetail;
   1750    }
   1751 
   1752 * **Primary key:** ``[pursePub]``
   1753 * **Deletion groups:** ``[peer-push-debit]``
   1754 
   1755 Merge strategy
   1756 ++++++++++++++
   1757 
   1758 Store all ``abortReason`` in the database.
   1759 
   1760 Peer-push-debit done
   1761 ~~~~~~~~~~~~~~~~~~~~
   1762 
   1763 An outgoing push payment initiated by the user completes successfully.
   1764 
   1765 .. ts:def:: PeerPushDebitDoneInc
   1766 
   1767    interface PeerPushDebitDoneInc {
   1768      type: "peer-push-debit-done";
   1769      pursePub: EddsaPublicKey;
   1770    }
   1771 
   1772 * **Primary key:** ``[pursePub]``
   1773 * **Deletion groups:** ``[peer-push-debit]``
   1774 
   1775 Merge strategy
   1776 ++++++++++++++
   1777 
   1778 No merge is required, a peer-push-debit payment can only succeed once.
   1779 
   1780 Peer-push-debit fail
   1781 ~~~~~~~~~~~~~~~~~~~~
   1782 
   1783 An outgoing push payment initiated by the user fails.
   1784 
   1785 .. ts:def:: PeerPushDebitFailInc
   1786 
   1787    interface PeerPushDebitFailInc {
   1788      type: "peer-push-debit-fail";
   1789      pursePub: EddsaPublicKey;
   1790      failReason: TalerErrorDetail;
   1791    }
   1792 
   1793 * **Primary key:** ``[pursePub]``
   1794 * **Deletion groups:** ``[peer-push-debit]``
   1795 
   1796 Merge strategy
   1797 ++++++++++++++
   1798 
   1799 Store all ``failReason`` in the database.
   1800 
   1801 Start peer-pull-debit
   1802 ~~~~~~~~~~~~~~~~~~~~~
   1803 
   1804 User confirms a payment request from another wallet.
   1805 
   1806 .. ts:def:: PeerPullDebitDoneInc
   1807 
   1808    interface PeerPullDebitDoneInc {
   1809      type: "peer-pull-debit-start";
   1810      peerPullDebitId: string;
   1811      pursePub: EddsaPublicKey;
   1812      exchangeBaseUrl: string;
   1813      amount: AmountString;
   1814      contractTermsHash: HashCode; // blob
   1815      timestampCreated: TalerPreciseTimestamp;
   1816      contractPriv: EddsaPrivateKey;
   1817      totalCostEstimated: AmountString;
   1818    }
   1819 
   1820 * **Primary key:** ``[peerPullDebitId]``
   1821 * **Deletion groups:** ``[peer-pull-debit]``
   1822 
   1823 Merge strategy
   1824 ++++++++++++++
   1825 
   1826 Last write wins, since the parameters of a peer-pull-debit transaction are
   1827 expected to always remain constant. However, ``peerPullDebitId`` must be
   1828 derived from the ``exchangeBaseUrl`` and ``pursePub``.
   1829 
   1830 Abort peer-pull-debit
   1831 ~~~~~~~~~~~~~~~~~~~~~
   1832 
   1833 User aborts a payment to another wallet.
   1834 
   1835 .. ts:def:: PeerPullDebitAbortInc
   1836 
   1837    interface PeerPullDebitAbortInc {
   1838      type: "peer-pull-debit-abort";
   1839      peerPullDebitId: string;
   1840      abortReason?: TalerErrorDetail;
   1841    }
   1842 
   1843 * **Primary key:** ``[peerPullDebitId]``
   1844 * **Deletion groups:** ``[peer-pull-debit]``
   1845 
   1846 Merge strategy
   1847 ++++++++++++++
   1848 
   1849 Store all ``abortReason`` in the database.
   1850 
   1851 Peer-pull-debit done
   1852 ~~~~~~~~~~~~~~~~~~~~
   1853 
   1854 A payment to another wallet completes successfully.
   1855 
   1856 .. ts:def:: PeerPullDebitDoneInc
   1857 
   1858    interface PeerPullDebitDoneInc {
   1859      type: "peer-pull-debit-done";
   1860      peerPullDebitId: string;
   1861    }
   1862 
   1863 * **Primary key:** ``[peerPullDebitId]``
   1864 * **Deletion groups:** ``[peer-pull-debit]``
   1865 
   1866 Merge strategy
   1867 ++++++++++++++
   1868 
   1869 No merge is required, a peer-pull-debit payment can only succeed once.
   1870 
   1871 Peer-pull-debit fail
   1872 ~~~~~~~~~~~~~~~~~~~~
   1873 
   1874 A payment to another wallet fails.
   1875 
   1876 .. ts:def:: PeerPullDebitFailInc
   1877 
   1878    interface PeerPullDebitFailInc {
   1879      type: "peer-pull-debit-fail";
   1880      peerPullDebitId: string;
   1881      failReason: TalerErrorDetail;
   1882    }
   1883 
   1884 * **Primary key:** ``[peerPullDebitId]``
   1885 * **Deletion groups:** ``[peer-pull-debit]``
   1886 
   1887 Merge strategy
   1888 ++++++++++++++
   1889 
   1890 Store all ``failReason`` in the database.
   1891 
   1892 Start peer-pull-credit
   1893 ~~~~~~~~~~~~~~~~~~~~~~
   1894 
   1895 User requests money to another wallet.
   1896 
   1897 .. ts:def:: PeerPullCreditStartInc
   1898 
   1899    interface PeerPullCreditStartInc {
   1900      type: "peer-pull-credit-start";
   1901      exchangeBaseUrl: string;
   1902      amount: AmountString;
   1903      estimatedAmountEffective: AmountString;
   1904      pursePub: EddsaPublicKey;
   1905      pursePriv: EddsaPrivateKey;
   1906      contractTermsHash: HashCode; // blob
   1907      mergePub: EddsaPublicKey;
   1908      mergePriv: EddsaPrivateKey;
   1909      contractPub: EddsaPublicKey;
   1910      contractPriv: EddsaPrivateKey;
   1911      contractEncNonce: string;
   1912      mergeTimestamp: TalerPreciseTimestamp;
   1913      mergeReserveRowId: number;
   1914      withdrawalGroupId?: string;
   1915    }
   1916 
   1917 * **Primary key:** ``[pursePub]``
   1918 * **Deletion groups:** ``[peer-pull-credit]``
   1919 
   1920 Merge strategy
   1921 ++++++++++++++
   1922 
   1923 No merge is required, all peer-pull-credit payments are independent from each
   1924 other.
   1925 
   1926 Abort peer-pull-credit
   1927 ~~~~~~~~~~~~~~~~~~~~~~
   1928 
   1929 User aborts request to another wallet.
   1930 
   1931 .. ts:def:: PeerPullCreditAbortInc
   1932 
   1933    interface PeerPullCreditAbortInc {
   1934      type: "peer-pull-credit-abort";
   1935      pursePub: EddsaPublicKey;
   1936      abortReason?: TalerErrorInfo;
   1937    }
   1938 
   1939 * **Primary key:** ``[pursePub]``
   1940 * **Deletion groups:** ``[peer-pull-credit]``
   1941 
   1942 Merge strategy
   1943 ++++++++++++++
   1944 
   1945 Store all ``failReason`` in the database.
   1946 
   1947 Peer-pull-credit done
   1948 ~~~~~~~~~~~~~~~~~~~~~
   1949 
   1950 A request to another wallet completes successfully (i.e. money is received).
   1951 
   1952 .. ts:def:: PeerPullCreditDoneInc
   1953 
   1954    interface PeerPullCreditDoneInc {
   1955      type: "peer-pull-credit-done";
   1956      pursePub: EddsaPublicKey;
   1957    }
   1958 
   1959 * **Primary key:** ``[pursePub]``
   1960 * **Deletion groups:** ``[peer-pull-credit]``
   1961 
   1962 Merge strategy
   1963 ++++++++++++++
   1964 
   1965 No merge is required, a peer-pull-credit payment can only succeed once.
   1966 
   1967 Peer-pull-credit fail
   1968 ~~~~~~~~~~~~~~~~~~~~~
   1969 
   1970 A request to another wallet fails.
   1971 
   1972 .. ts:def:: PeerPullCreditFailInc
   1973 
   1974    interface PeerPullCreditFailInc {
   1975      type: "peer-pull-credit-fail";
   1976      pursePub: EddsaPublicKey;
   1977      failReason: TalerErrorInfo;
   1978    }
   1979 
   1980 * **Primary key:** ``[pursePub]``
   1981 * **Deletion groups:** ``[peer-pull-credit]``
   1982 
   1983 Merge strategy
   1984 ++++++++++++++
   1985 
   1986 Store all ``failReason`` in the database.
   1987 
   1988 Start a refresh
   1989 ~~~~~~~~~~~~~~~
   1990 
   1991 The wallet melts the remainder of one or more coins into fresh ones -- as
   1992 change after a payment, or to renew a coin whose denomination is about to
   1993 expire.
   1994 
   1995 The group carries the plan; how far it has got lives in the per-coin sessions
   1996 below.  A restored group is what lets a wallet that melted a coin and then
   1997 lost the device still collect the change: the exchange holds the first melt
   1998 commitment, and a wallet that re-melted with a fresh seed could not reveal
   1999 against it.
   2000 
   2001 .. ts:def:: RefreshStartInc
   2002 
   2003    interface RefreshStartInc {
   2004      type: "refresh-start";
   2005      refreshGroupId: string;
   2006      currency: string;
   2007      reason: string;
   2008      originatingTransactionId?: string;
   2009      oldCoinPubs: string[];
   2010      inputPerCoin: AmountString[];
   2011      expectedOutputPerCoin: AmountString[];
   2012      timestampCreated: TalerPreciseTimestamp;
   2013    }
   2014 
   2015 * **Primary key:** ``[refreshGroupId]``
   2016 * **Deletion groups:** ``[refreshes]``
   2017 
   2018 Merge strategy
   2019 ++++++++++++++
   2020 
   2021 Last write wins: the plan of a refresh group never changes.
   2022 
   2023 Refresh session
   2024 ~~~~~~~~~~~~~~~
   2025 
   2026 The melt of one coin of a refresh group.
   2027 
   2028 Everything the reveal step needs -- the fresh coins' key material included --
   2029 is derived from ``sessionPublicSeed`` together with the old coin and the
   2030 chosen denominations, all of which travel here, so this is the part of a
   2031 refresh that has to be backed up.
   2032 
   2033 .. ts:def:: RefreshSessionInc
   2034 
   2035    interface RefreshSessionInc {
   2036      type: "refresh-session";
   2037      refreshGroupId: string;
   2038      coinIndex: number;
   2039      sessionPublicSeed?: string;
   2040      refreshProtocolVersion?: number;
   2041      amountRefreshOutput: AmountString;
   2042      newDenoms: { denomPubHash: string; count: number }[];
   2043      norevealIndex?: number;
   2044    }
   2045 
   2046 * **Primary key:** ``[refreshGroupId, coinIndex]``
   2047 * **Deletion groups:** ``[refreshes]``
   2048 
   2049 Merge strategy
   2050 ++++++++++++++
   2051 
   2052 Last write wins: the session is written once, when the coin is melted.
   2053 
   2054 Refresh done
   2055 ~~~~~~~~~~~~
   2056 
   2057 Every coin of the group has been melted and the fresh coins collected.
   2058 
   2059 .. ts:def:: RefreshDoneInc
   2060 
   2061    interface RefreshDoneInc {
   2062      type: "refresh-done";
   2063      refreshGroupId: string;
   2064      timestampFinished: TalerPreciseTimestamp;
   2065    }
   2066 
   2067 * **Primary key:** ``[refreshGroupId]``
   2068 * **Deletion groups:** ``[refreshes]``
   2069 
   2070 Refresh failed
   2071 ~~~~~~~~~~~~~~
   2072 
   2073 The refresh could not be completed.
   2074 
   2075 .. ts:def:: RefreshFailInc
   2076 
   2077    interface RefreshFailInc {
   2078      type: "refresh-fail";
   2079      refreshGroupId: string;
   2080      failReason: TalerErrorDetail;
   2081    }
   2082 
   2083 * **Primary key:** ``[refreshGroupId]``
   2084 * **Deletion groups:** ``[refreshes]``
   2085 
   2086 Derived operations: refunds, recoups and denomination losses
   2087 ------------------------------------------------------------
   2088 
   2089 The three families below differ from every other one in this document: the
   2090 wallet does not start them, it *learns* about them.  A refund is the
   2091 merchant's answer to a refund query, a recoup is forced by an exchange
   2092 revoking a denomination, and a denomination loss is what the wallet has to
   2093 write off when a denomination expires or is withdrawn from circulation.
   2094 
   2095 Any wallet holding the coins can ask the same question and get the same
   2096 answer, which is what decides how they are backed up: **only a finished one
   2097 travels, and it restores as finished.** Backing up a pending one would hand
   2098 the second device work on an operation it cannot see the whole of -- it would
   2099 go and query a merchant about a refund that is already settled on the first
   2100 device -- and would leave the user looking at an operation that is long over
   2101 elsewhere but "pending" here.  A pending one is simply not collected, and
   2102 keeps no origin block, so a later pass offers it up once it has finished.
   2103 
   2104 Refund
   2105 ~~~~~~
   2106 
   2107 A refund the merchant granted, as it finally stood.
   2108 
   2109 The refund *items* (one per coin) are deliberately not carried: nothing
   2110 outside the refund query itself reads them, the transaction is rendered
   2111 entirely from the group, and their identity is the merchant's
   2112 (``coin_pub``/``rtransaction_id``), so a wallet that does query gets the same
   2113 ones back.
   2114 
   2115 .. ts:def:: RefundInc
   2116 
   2117    interface RefundInc {
   2118      type: "refund";
   2119      refundGroupId: string;
   2120      // The purchase this refunds; restored as the transaction it points
   2121      // at, and not applied at all when that purchase is not there.
   2122      proposalId: string;
   2123      outcome: DerivedOutcome;
   2124      amountRaw: AmountString;
   2125      amountEffective: AmountString;
   2126      timestampCreated: TalerPreciseTimestamp;
   2127    }
   2128 
   2129 .. ts:def:: DerivedOutcome
   2130 
   2131    // How one of the derived operations ended.  A wire string rather than
   2132    // the wallet's numeric status enum, which is a database detail.
   2133    type DerivedOutcome = "done" | "failed" | "aborted" | "expired";
   2134 
   2135 * **Primary key:** ``[refundGroupId]``
   2136 * **Deletion groups:** ``[refunds, payments]``
   2137 
   2138 Merge strategy
   2139 ++++++++++++++
   2140 
   2141 Last write wins: the increment describes one finished operation, and there is
   2142 nothing to reconcile field by field.
   2143 
   2144 Recoup
   2145 ~~~~~~
   2146 
   2147 Coins reclaimed from an exchange that revoked their denomination.
   2148 
   2149 What the recoup *did* to the coins reaches the other wallets as coin
   2150 increments; this is what makes the operation itself appear.  Its per-coin
   2151 progress is not carried -- it describes a run the other wallet did not make --
   2152 and a restored recoup is marked finished for every coin, so that the second
   2153 device does not go and re-submit somebody else's recoup.
   2154 
   2155 .. ts:def:: RecoupInc
   2156 
   2157    interface RecoupInc {
   2158      type: "recoup";
   2159      recoupGroupId: string;
   2160      exchangeBaseUrl: string;
   2161      outcome: DerivedOutcome;
   2162      // The coins that were recouped, in the order the group listed them.
   2163      coinPubs: string[];
   2164      timestampStarted: TalerPreciseTimestamp;
   2165      timestampFinished?: TalerPreciseTimestamp;
   2166    }
   2167 
   2168 * **Primary key:** ``[recoupGroupId]``
   2169 * **Deletion groups:** ``[recoups, coins]``
   2170 
   2171 Merge strategy
   2172 ++++++++++++++
   2173 
   2174 Last write wins.
   2175 
   2176 Denomination loss
   2177 ~~~~~~~~~~~~~~~~~
   2178 
   2179 A denomination the wallet had to write off, with the coins it cost.
   2180 
   2181 Unlike the two above this one is not merely history: until the other wallets
   2182 learn of it they keep the affected coins in their balance, and the two devices
   2183 disagree about how much money the user has.  The coins themselves carry the
   2184 same news -- their status becomes ``denom-loss`` -- and this is what makes the
   2185 transaction appear.
   2186 
   2187 ``denomLossEventId`` is **derived from the loss** rather than drawn at random.
   2188 Both wallets notice the same expiry on their own, each updating the exchange
   2189 and seeing the same denominations go; with random identifiers the user would
   2190 end up with the same loss listed twice.
   2191 
   2192 .. code-block:: text
   2193 
   2194    denom_loss_event_id = SHA512(exchange_base_url || 0 || event_type || 0 ||
   2195                                 sorted(denom_pub_hashes) each || 0)[0:32]
   2196 
   2197 .. ts:def:: DenomLossInc
   2198 
   2199    interface DenomLossInc {
   2200      type: "denom-loss";
   2201      denomLossEventId: string;
   2202      currency: string;
   2203      exchangeBaseUrl: string;
   2204      denomPubHashes: string[];
   2205      // "denom-expired", "denom-vanished", "denom-revoked",
   2206      // "denom-unoffered".
   2207      eventType: string;
   2208      // "aborted" when the loss turned out to be reversible.
   2209      outcome: "done" | "aborted";
   2210      amount: AmountString;
   2211      timestampCreated: TalerPreciseTimestamp;
   2212    }
   2213 
   2214 * **Primary key:** ``[denomLossEventId]``
   2215 * **Deletion groups:** ``[denom-losses, denominations]``
   2216 
   2217 Merge strategy
   2218 ++++++++++++++
   2219 
   2220 Last write wins.
   2221 
   2222 Item deletion
   2223 -------------
   2224 
   2225 Due to privacy considerations within our use case, rather than using classical
   2226 CRDT-style tombstones to encode deletion operations into blocks, a novel
   2227 approach was conceived, whereby each item (e.g. an exchange) in the local
   2228 wallet database to be included in the backup keeps a list of UUIDs of the
   2229 "origin" blocks that have inserted or updated it.
   2230 
   2231 .. code-block:: typescript
   2232 
   2233    originBlocks: Set<BlockUuid>;
   2234 
   2235 Using this approach, a deletion of an item would simply consist of locating
   2236 the origin blocks referenced in its UUID list, and deleting the corresponding
   2237 insertion/update operations from all of them.
   2238 
   2239 In order to prevent wallets from mistakenly reinserting an item into the
   2240 backup that was previously deleted by another wallet, an item is deemed
   2241 deleted iff it no longer appears in any of its origin blocks, allowing it to
   2242 be safely removed from the local database as well.
   2243 
   2244 Mechanically, a wallet deletes an item by scrubbing its increments out of the
   2245 pending buffer and rewriting every origin block that still carries them: a
   2246 block that keeps other content is replaced in place (``PUT``, under its
   2247 original nonce), one that becomes empty is removed from the linked list
   2248 (``DELETE``, relinking its neighbours).  A block rewritten in place keeps its
   2249 nonce, so the other wallets detect the change only by noticing that the
   2250 block's hash no longer matches their local copy; a deleted block shows up as a
   2251 gap in the linked list.  On either signal a wallet re-applies the whole linked
   2252 list and drops every item that no longer appears in any origin block, which is
   2253 what makes deletions propagate across the sync group.
   2254 
   2255 Deletion groups
   2256 ~~~~~~~~~~~~~~~
   2257 
   2258 A resource within its deletion group is identified by its primary key. When
   2259 the resource in question is deleted, all references to this resource within
   2260 the resource group must also be deleted from the blocks listed in the
   2261 ``originBlocks`` field of its database record.
   2262 
   2263 For example, when deleting a denomination, all the coin insertions of that
   2264 denomination must also be deleted from the backup, since they are in the
   2265 ``denominations`` deletion group and thus contain a reference to a
   2266 denomination. In turn, all the sign and spend operations of the deleted coins
   2267 must also be deleted, since they are in the ``coins`` deletion group and thus
   2268 contain a reference to a coin.
   2269 
   2270 Backup process
   2271 --------------
   2272 
   2273 Collecting increments
   2274 ~~~~~~~~~~~~~~~~~~~~~
   2275 
   2276 Recording runs inside the very transaction that performs the withdrawal, the
   2277 payment or the deposit, which is what makes wallet state and backup state
   2278 commit together -- and also means that anything the recording throws takes
   2279 that operation down with it.  It must therefore be impossible for the backup
   2280 to fail an operation: the eager recording is an *optimisation*, not the
   2281 guarantee.  A record whose increment never made it keeps its ``originBlocks``
   2282 unset, which is exactly what the full collection pass looks for, so a failure
   2283 costs a delay and nothing else.  Recording, waking the cycle and queueing a
   2284 deletion all log and swallow; the critical-point hold fails open.
   2285 
   2286 The same applies to key material the wallet *derives* for an operation.  A
   2287 reserve key pair comes from the reserve seed, so a seed the wallet cannot
   2288 decode would otherwise block every withdrawal, permanently, since the seed is
   2289 stored.  An unusable seed instead falls back to a random reserve key pair,
   2290 which the backup carries as ``reservePriv`` the way it does for reserves that
   2291 predate the seed, and the seed itself is left untouched -- reserves already
   2292 derived from it are named by their index, so replacing it would make them
   2293 underivable elsewhere.
   2294 
   2295 Stored key material is checked before it is decoded, because the two Crockford
   2296 base32 decoders a wallet may run on do not agree: the JavaScript one ignores
   2297 trailing padding bits that are not zero, while the native (qtart) one rejects
   2298 the string outright.  A value decoded unchecked therefore works in a browser
   2299 extension and throws on a phone.  Re-encoding the decoded bytes and comparing
   2300 settles it on either runtime, and is what the restore path uses to refuse a
   2301 malformed seed rather than store one.
   2302 
   2303 Wallet transactions record what they changed by appending increments to a
   2304 pending buffer, held in the wallet's backup configuration record. The
   2305 recording happens **within the same database transaction that performs the
   2306 change**, so that the change and the increment describing it commit together.
   2307 A wallet can therefore never end up in a state that its backup does not know
   2308 about, however abruptly it is shut down.
   2309 
   2310 A wallet that has not set up backup yet has no encryption key to protect the
   2311 increments with, so recording is a no-op rather than an error.
   2312 
   2313 An increment that another record depends on must not reach the group later
   2314 than the record itself.  The denomination of a coin is the case that
   2315 matters: a restored coin only counts towards the balance once the
   2316 denomination it names is in the database, since that is where the
   2317 availability row takes its currency and value from.  Denominations are not
   2318 written by a transaction of their own, so recording a coin records its
   2319 denomination with it -- once per denomination, however many coins of it a
   2320 withdrawal makes -- and the two travel in the same block, where the
   2321 denomination section is applied before the coin section.  Leaving the
   2322 denomination to the full collection pass instead would let a coin reach
   2323 the other wallets of the group up to a day ahead of it.
   2324 
   2325 The backup cycle
   2326 ~~~~~~~~~~~~~~~~
   2327 
   2328 One cycle takes whatever increments have accumulated, packs them into a block,
   2329 and appends that block to the account's linked list:
   2330 
   2331 1. In a single database transaction, move the pending increments out of the
   2332    buffer and into an *in-flight block*, storing its nonce, hash, contents and
   2333    the nonce of the block it is to be appended after.
   2334 2. Upload any blobs the block references, then the block itself.
   2335 3. Once the provider has acknowledged the block, discard the in-flight block
   2336    and advance the pointer to the last acknowledged block.
   2337 
   2338 The hand-over in step 1 is what makes the cycle resilient: the increments are
   2339 never absent from both the buffer and a block. A wallet that dies at any point
   2340 either finds increments still pending, or finds an in-flight block and retries
   2341 it — under its **original nonce**, which the server answers with ``304 Not
   2342 modified`` if the upload did in fact land. Increments are thus neither lost
   2343 nor backed up twice, and a cycle that has packed a block always retries it
   2344 before packing new increments, so the linked list stays ordered.
   2345 
   2346 A cycle packs at most one block, and bounds its size.  The server refuses
   2347 an upload beyond its ``storage_limit_in_megabytes`` with ``413``, and a
   2348 block over that limit is not a transient failure: the wallet would re-upload
   2349 the very same block on every cycle and never get past it.  The pack
   2350 therefore stops well below any plausible server limit and leaves whatever
   2351 does not fit in the pending buffer, which the next cycle takes -- a wallet
   2352 handing over a long history (the full collection pass on a well-used
   2353 device) sends it as a run of blocks rather than as one oversized one, and
   2354 reports progress rather than backing off between them.  A ``413`` that
   2355 happens anyway is answered by putting the block's increments back and
   2356 packing the next one smaller, since retrying it unchanged can never
   2357 succeed.
   2358 
   2359 A cycle also pulls the account's linked list before packing new increments,
   2360 applying any blocks it has not seen before (see "Restore process" below), so
   2361 that new blocks are appended after the current end of the list.
   2362 
   2363 An account that has not been paid for yet answers every request with ``402
   2364 Payment required``, and only the upload endpoints carry the ``Taler:`` header
   2365 with a ``taler://pay/...`` URI.  A cycle that is answered this way while
   2366 pulling therefore pushes whatever it has pending, so the payment is settled —
   2367 automatically when the annual fee is zero — and subsequent writes are
   2368 accepted.
   2369 
   2370 Backup schedule
   2371 ---------------
   2372 
   2373 A backup runs at *critical points* of wallet operations, and on a schedule
   2374 otherwise.
   2375 
   2376 A critical point is one past which losing the device loses money or user data
   2377 that cannot be reconstructed. The canonical example is a withdrawal: coin
   2378 secrets are derived from the withdrawal group's seed, so a backup is triggered
   2379 once every planchet has been generated and persisted but **before** the
   2380 exchange is asked to sign them. Past that point the exchange considers the
   2381 coins withdrawn while a wallet restored from an older backup could no longer
   2382 reconstruct them.
   2383 
   2384 A cycle is triggered after the recording transaction commits; if the wallet
   2385 stops before it runs, the increments simply stay pending until the next cycle.
   2386 Independently, a periodic task runs a cycle every hour, covering increments
   2387 whose trigger never fired, e.g. because the wallet was offline or the
   2388 operation has no critical point.  A cycle that could not reach the provider is
   2389 retried after five minutes, and one that is waiting for the account payment to
   2390 be prepared after thirty seconds -- the payment is what unlocks every upload,
   2391 so it is worth retrying as soon as the provider's merchant backend recovers.
   2392 
   2393 Waking the cycle is not always enough. Past a critical point the wallet has
   2394 already revealed key material to somebody else -- the exchange has signed the
   2395 planchets, the purse exists and can be paid into -- and the cycle runs
   2396 concurrently, so the operation would go ahead regardless.  Those points
   2397 therefore *hold*: the task returns to the scheduler and is retried, and only
   2398 proceeds once the pending buffer has reached the provider.  The hold is
   2399 skipped when the account is unpaid, since no cycle can drain the buffer until
   2400 the user pays and freezing every such transaction would be the worse failure.
   2401 
   2402 Each request for a cycle names how much is at stake, and the most urgent
   2403 reason asked for since the last cycle that reached the provider is what
   2404 decides how hard a *failing* cycle retries:
   2405 
   2406 * ``irrecoverable-secret`` -- key material a lost device would turn into lost
   2407   money.  Retried after fifteen seconds: the transaction that produced it is
   2408   held until the buffer drains, so a longer wait is also how long that
   2409   transaction sits still.
   2410 * ``transaction-milestone`` -- a state the user would notice losing, but one
   2411   that can be reconstructed.
   2412 * ``account-payment`` -- the sync account's own payment moved; nothing of the
   2413   user's is at stake.
   2414 
   2415 The last two fall back to the ordinary five-minute retry.  The urgency is not
   2416 persisted: after a restart the pending increments are still there and the
   2417 critical points ask again on their next retry, so it re-establishes itself
   2418 rather than having to be reconstructed.
   2419 
   2420 Full collection pass
   2421 ~~~~~~~~~~~~~~~~~~~~
   2422 
   2423 Eager recording covers every transaction family, but a record can still exist
   2424 that no transaction ever reported: one that predates the backup, or one of a
   2425 kind whose creation path bypasses the record handle.  A periodic *full
   2426 collection pass* is the safety net: it walks every record kind the backup
   2427 manages (the ``backupSources`` of ``sources.ts``) and turns the records that
   2428 have never been backed up into "start" increments.
   2429 
   2430 The pass is expensive -- it reads every denomination, exchange, bank account
   2431 and transaction the wallet holds -- so it does not run on every cycle.  It
   2432 runs when a watermark, ``lastFullCollection`` in the wallet's backup
   2433 configuration record, is older than 24 hours (or absent, i.e. never run).  A
   2434 cycle that woke from a critical point therefore stays cheap while still
   2435 backing up whatever the transactions themselves reported.
   2436 
   2437 A forced cycle (see ``runBackupCycle`` in the wallet-core API below) bypasses
   2438 the watermark and runs the pass regardless.  This is the tool for developer
   2439 diagnostics: everything the pass would collect is reported by
   2440 ``getBackupDiagnostics`` before the cycle runs, so the two requests together
   2441 show exactly what is waiting to be backed up and what a forced cycle would
   2442 add.
   2443 
   2444 Restore process
   2445 ---------------
   2446 
   2447 Restoring a wallet on a (fresh) device is the pull half of the backup cycle,
   2448 driven by a recovery document from ``getBackupRecovery``:
   2449 
   2450 1. ``loadBackupRecovery`` installs the recovery's root key and providers, and
   2451    drops the wallet's own block pointers, so the device starts from nothing.
   2452 2. Once the user activates a recovered provider (``addBackupProvider`` with
   2453    ``activate``), the backup cycle downloads the account's linked list,
   2454    decodes each block it has not seen before, CRDT-applies its increments to
   2455    the local database -- recording the block's nonce in the ``originBlocks``
   2456    of every record it touched -- and stores the blocks locally.
   2457 
   2458 Because the same root key derives the same per-provider account keys, a
   2459 recovering wallet sees exactly the blocks any other wallet in the group
   2460 uploaded and applies them with the same merge rules, so all devices converge
   2461 on the same state.
   2462 
   2463 Two things a restored record cannot simply carry are worked out again on
   2464 the restoring device:
   2465 
   2466 * A coin that arrives before the denomination it names cannot be counted,
   2467   because the availability row cannot be written without it.  Applying a
   2468   denomination therefore recounts the coins of that denomination that are
   2469   already in the database, so a coin whose denomination travels in a later
   2470   block -- or in a block written by another wallet -- still reaches the
   2471   balance instead of being dropped from it for good.
   2472 * A pending withdrawal's transfer instructions -- the exchange's credit
   2473   accounts, and the transfer options the user actually pays with -- are
   2474   derived from the exchange, the instructed amount and the reserve key
   2475   pair, and an option registered with a prepared-transfer service carries
   2476   an expiry.  A restoring wallet derives them again whenever the ones it
   2477   restored are absent or expired, and does so *before* it queries the
   2478   reserve: until the transfer has been made the reserve does not exist at
   2479   the exchange yet, so a wallet that waited for the reserve status would
   2480   never get as far as showing the user something to pay with.
   2481 
   2482 Restore schedule
   2483 ----------------
   2484 
   2485 Restoring happens on demand: it starts when a recovery document is loaded and
   2486 the recovered provider is activated.  Afterwards the restored wallet is kept
   2487 up to date by the same periodic backup task as every other wallet -- the pull
   2488 half runs on every cycle, so changes made by other devices are picked up at
   2489 the cycle interval.
   2490 
   2491 Wallet-core API
   2492 ---------------
   2493 
   2494 Backup providers and the wallet's backup key are managed through the
   2495 wallet-core API. All requests below are available on every platform.  The
   2496 request handlers described here are implemented; the collection and scheduling
   2497 mechanisms described above drive them.
   2498 
   2499 .. ts:def:: AddBackupProviderRequest
   2500 
   2501    interface AddBackupProviderRequest {
   2502      backupProviderBaseUrl: string;
   2503 
   2504      name: string;
   2505 
   2506      // Activate the provider.  Should only be done after
   2507      // the user has reviewed the provider.
   2508      activate?: boolean;
   2509    }
   2510 
   2511 The cycle never *waits* for the account payment.  Downloading the provider's
   2512 proposal and paying it are the purchase's own task, so the cycle only ever
   2513 looks at where that purchase has got to -- confirming it when it is waiting
   2514 for a decision, and otherwise leaving it alone -- and comes back when the
   2515 purchase transitions, or on its retry interval.  Every step is therefore
   2516 idempotent and survives a wallet that stops in the middle.
   2517 
   2518 ``addBackupProvider`` registers a sync server: it stores a provider record and
   2519 -- when ``activate`` is set -- makes it the active sync target and wakes the
   2520 backup cycle.  The request itself does not talk to the provider and returns as
   2521 soon as the record is written; an unreachable provider, or one that is not a
   2522 sync server, therefore shows up as a failing (and retrying) cycle rather than
   2523 as an error from this request.
   2524 
   2525 The first cycle is what learns the provider's terms (it fetches ``/config``
   2526 and reports the result with the ``terms-fetched`` phase of the
   2527 ``backup-status`` notification) and what settles the account payment: a sync
   2528 account only exists once it has been paid for, and the server rejects every
   2529 upload (even at a zero annual fee) until then.  A zero-fee account is paid
   2530 automatically; any other account produces a payment transaction that the user
   2531 confirms from the wallet, and the ``payment-required`` phase of the
   2532 notification carries its ``taler://pay/...`` URI.  Clients follow all of this
   2533 through the notifications, not through this request's response:
   2534 
   2535 .. ts:def:: AddBackupProviderResponse
   2536 
   2537    interface AddBackupProviderResponse {
   2538      status: "ok";
   2539    }
   2540 
   2541 ``removeBackupProvider`` takes a `RemoveBackupProviderRequest` naming the
   2542 provider by base URL and returns an empty object.
   2543 
   2544 .. ts:def:: RemoveBackupProviderRequest
   2545 
   2546    interface RemoveBackupProviderRequest {
   2547      backupProviderBaseUrl: string;
   2548    }
   2549 
   2550 ``getBackupInfo`` reports the wallet's backup identity and the state of each
   2551 known provider, including its terms, payment status and the outcome of the
   2552 last backup attempt.
   2553 
   2554 .. ts:def:: BackupInfo
   2555 
   2556    interface BackupInfo {
   2557      walletRootPub: string;
   2558      providers: ProviderInfo[];
   2559    }
   2560 
   2561 ``ProviderInfo`` describes one known provider and the state of the wallet's
   2562 account on it:
   2563 
   2564 .. ts:def:: ProviderInfo
   2565 
   2566    interface ProviderInfo {
   2567      active: boolean;
   2568      backupProviderBaseUrl: string;
   2569      name: string;
   2570      terms?: BackupProviderTerms;
   2571 
   2572      // Why the last cycle failed, when it did.  Only for the active
   2573      // provider: the cycle statistics describe the wallet's last cycle,
   2574      // and that ran against the provider it syncs to.
   2575      lastError?: TalerErrorDetail;
   2576      lastSuccessfulBackupTimestamp?: TalerPreciseTimestamp;
   2577      lastAttemptedBackupTimestamp?: TalerPreciseTimestamp;
   2578 
   2579      // Payment transactions opened for this account, most recent last.
   2580      paymentTransactionIds: string[];
   2581      // Deprecated alias of paymentTransactionIds, with the same contents,
   2582      // for user interfaces built against an older wallet-core.
   2583      paymentProposalIds: string[];
   2584      paymentStatus: ProviderPaymentStatus;
   2585 
   2586      // What the provider reports it holds for the account, from the
   2587      // account status lookup.  Absent until a cycle has managed to ask,
   2588      // and for providers older than sync protocol v4.
   2589      storageUsedBytes?: number;
   2590      blockCount?: number;
   2591    }
   2592 
   2593 .. ts:def:: BackupProviderTerms
   2594 
   2595    interface BackupProviderTerms {
   2596      supportedProtocolVersion: string;
   2597      annualFee: AmountString;
   2598      storageLimitInMegabytes: number;
   2599    }
   2600 
   2601 The provider's ``paymentStatus`` reflects how far the account payment has
   2602 gotten, based on the payment transaction the wallet opened for it:
   2603 
   2604 .. ts:def:: ProviderPaymentStatus
   2605 
   2606    type ProviderPaymentStatus =
   2607      | { type: "unpaid" }
   2608      | { type: "pending"; talerUri?: string }
   2609      | { type: "insufficient-balance"; amount: AmountString }
   2610      | { type: "paid"; paidUntil: AbsoluteTime }
   2611      | { type: "terms-changed";
   2612          paidUntil: AbsoluteTime;
   2613          oldTerms: BackupProviderTerms;
   2614          newTerms: BackupProviderTerms };
   2615 
   2616 ``getBackupRecovery`` returns the secret needed to restore the wallet on
   2617 another device, along with the providers to fetch the blocks from. It is what
   2618 the user backs up out of band, and what a restoring wallet is fed.
   2619 
   2620 .. ts:def:: BackupRecovery
   2621 
   2622    interface BackupRecovery {
   2623      walletRootPriv: string;
   2624      providers: {
   2625        name: string;
   2626        url: string;
   2627      }[];
   2628 
   2629      // The same data as a self-contained plain text, for writing down by
   2630      // hand or saving to a file.  Produced here and *not* consumed by
   2631      // loadBackupRecovery, which reads the structured fields above.
   2632      paperKey?: string;
   2633    }
   2634 
   2635 The paper key is line-oriented, so that a line is the unit to copy, parse and
   2636 transpose:
   2637 
   2638 .. code-block:: text
   2639 
   2640    TALER-PAPERKEY:1
   2641    KEY: GXDG VQKT ...            (the root key, grouped in fours)
   2642    CHECK: a1b2c3d4               (first 8 hex digits of SHA-512(root key))
   2643    PROVIDER: https://sync.example.com/
   2644    URI: taler://restore/...      (the machine-readable form, LSD0006 5.7)
   2645 
   2646 The ``URI`` line is the canonical machine form: a device restoring from a scan
   2647 or a file needs nothing but that line.  The ``KEY`` / ``PROVIDER`` lines are
   2648 the human form, and the checksum catches a transcription error before it
   2649 silently restores a different -- empty -- sync group.
   2650 
   2651 ``loadBackupRecovery`` feeds such a recovery document into a wallet, which is
   2652 how a second (or replacing) device joins the sync group.  The wallet adopts
   2653 the recovery's root key -- the key every per-provider account key is derived
   2654 from, so adopting it *is* what joining the group means -- and adds the
   2655 recovery's providers.  There is no "keep my own key" variant: a wallet that
   2656 kept its own key would derive different account keys and so would not be in
   2657 the group at all.
   2658 
   2659 Adopting another root key also detaches the wallet from the group it was in:
   2660 the blocks it stored are encrypted under a key it no longer has, and the
   2661 ``originBlocks`` lists that reference them are meaningless.  Both are cleared.
   2662 That deliberately leaves the wallet's own records looking "never backed up",
   2663 which is what they are with respect to the group being joined: the full
   2664 collection pass then offers them up, instead of the pull's "deleted iff absent
   2665 from all origin blocks" sweep removing them for not appearing in the new
   2666 group's linked list.
   2667 
   2668 The providers are registered but not activated; the client activates one with
   2669 ``addBackupProvider`` (``activate: true``), and that is what starts the cycle
   2670 which pulls the backup.
   2671 
   2672 .. ts:def:: RecoveryLoadRequest
   2673 
   2674    interface RecoveryLoadRequest {
   2675      recovery: BackupRecovery;
   2676    }
   2677 
   2678 ``runBackupCycle`` runs a backup cycle now, instead of waiting for the
   2679 periodic task.  This is the dedicated "back up now" request; earlier
   2680 implementations triggered a cycle by re-adding the active provider.
   2681 
   2682 The request only *wakes* the cycle and returns an empty object immediately:
   2683 the cycle runs asynchronously (and is serialized against any other cycle),
   2684 reports its progress and outcome through the ``backup-status`` notifications,
   2685 and persists its statistics for ``getBackupDiagnostics``.  Clients track the
   2686 cycle through those, not through this request's response.
   2687 
   2688 .. ts:def:: RunBackupCycleRequest
   2689 
   2690    interface RunBackupCycleRequest {
   2691      // Run the full-collection pass even when its periodic watermark
   2692      // (24h since the last pass) has not elapsed.  Harmless -- the pass
   2693      // only reads the wallet database -- and user interfaces are
   2694      // expected to only expose it in developer mode.
   2695      force?: boolean;
   2696    }
   2697 
   2698 The statistics are persisted by the wallet after every cycle, whatever
   2699 triggered it, and are reported by ``getBackupDiagnostics`` as the "last cycle"
   2700 outcome.  The ``outcome`` field says how the cycle ended: ``"ok"`` (including
   2701 idle cycles with nothing to push), ``"payment-required"`` (the account is
   2702 unpaid) or ``"error"``.
   2703 
   2704 .. ts:def:: BackupCycleStats
   2705 
   2706    interface BackupCycleStats {
   2707      timestamp: TalerPreciseTimestamp;
   2708      // How the cycle ended: "ok", "payment-required" or "error".
   2709      outcome: "ok" | "payment-required" | "error";
   2710      // Why it failed, when the outcome is "error"; the same detail the
   2711      // notification carried, kept for a client that was not listening.
   2712      lastError?: TalerErrorDetail;
   2713 
   2714      // What the cycle pushed to the provider.
   2715      pushed: {
   2716        // Whether the full-collection pass ran in this cycle.
   2717        fullCollectionRan: boolean;
   2718        // Nonce of the block uploaded, if there was anything to upload.
   2719        blockNonce?: string;
   2720        incrementCount: number;
   2721        // Number of increments per increment type, keyed by the
   2722        // increment type's wire string (e.g. "payment-start").
   2723        incrementsByType: { [type: string]: number };
   2724        blobRefCount: number;
   2725      };
   2726 
   2727      // What the cycle's pull applied from the provider.
   2728      pulled: {
   2729        blocksApplied: number;
   2730        blocksSkipped: number;
   2731        incrementCount: number;
   2732        incrementsByType: { [type: string]: number };
   2733        blobRestoreCount: number;
   2734      };
   2735    }
   2736 
   2737 ``getBackupDiagnostics`` reports aggregated statistics about what the backup
   2738 holds: what a cycle would back up right now, and what the last cycle restored.
   2739 It is intended for developer tooling; user interfaces are expected to only
   2740 expose it in developer mode, but the request itself is harmless and available
   2741 on every platform.
   2742 
   2743 .. ts:def:: BackupDiagnostics
   2744 
   2745    interface BackupDiagnostics {
   2746      // The increments waiting in the eager pending buffer: what a normal
   2747      // (unforced) cycle would push right now.
   2748      pending: IncrementStatSummary;
   2749 
   2750      // The records the backup has never seen, which only the periodic
   2751      // full-collection pass picks up: what a forced cycle would add.
   2752      fullCollectionCandidates: IncrementStatSummary;
   2753 
   2754      // Outcome of the last backup cycle, when at least one has run.
   2755      lastCycle?: BackupCycleStats;
   2756    }
   2757 
   2758 .. ts:def:: IncrementStatSummary
   2759 
   2760    interface IncrementStatSummary {
   2761      incrementCount: number;
   2762      // Number of increments per increment type, keyed by the increment
   2763      // type's wire string.
   2764      incrementsByType: { [type: string]: number };
   2765      // Number of distinct blob references the increments carry.
   2766      blobRefCount: number;
   2767    }
   2768 
   2769 Account keys are not part of any of these payloads: they are derived from the
   2770 wallet root key and the provider's base URL, so each provider sees an
   2771 unlinkable account public key and only the root key has to be preserved.
   2772 
   2773 .. code-block:: text
   2774 
   2775    account_priv = KDF(32, wallet_root_priv,
   2776                       "taler-sync-account-key-salt", provider_base_url)
   2777 
   2778 Backup notifications
   2779 --------------------
   2780 
   2781 The wallet pushes a ``backup-status`` notification to its clients
   2782 (``NotificationType.BackupStatus``) as a backup cycle runs, through the
   2783 regular wallet notification listener.  Clients should use it instead of
   2784 polling ``getBackupInfo`` to track a cycle: it reports the phase the cycle is
   2785 in and, on the terminal phases, the outcome and the relevant counters.
   2786 
   2787 .. ts:def:: BackupStatusNotification
   2788 
   2789    interface BackupStatusNotification {
   2790      type: "backup-status";
   2791      providerBaseUrl: string;
   2792      // "started", "pulling", "pushing" and "terms-fetched" are progress
   2793      // phases; the cycle ends in exactly one of "completed", "error" and
   2794      // "payment-required".
   2795      phase: "started" | "pulling" | "pushing" | "terms-fetched" |
   2796             "completed" | "error" | "payment-required";
   2797      // Number of increments packed into the block being pushed
   2798      // (at "pushing").
   2799      pendingIncrementCount?: number;
   2800      // Number of blocks the pull applied (at "completed").
   2801      pulledBlocks?: number;
   2802      // Nonce of the block pushed (at "completed").
   2803      pushedBlockNonce?: string;
   2804      // Reason of the failure (at "error").
   2805      error?: TalerErrorDetail;
   2806      // taler://pay/... URI of the prepared account payment (at
   2807      // "payment-required"); absent when the provider answered a bare
   2808      // 402 without a pay URI.
   2809      talerUri?: string;
   2810      timestamp: TalerPreciseTimestamp;
   2811    }
   2812 
   2813 The wallet emits ``started`` when a cycle begins, ``pulling`` before the
   2814 linked list is fetched, ``pushing`` with the increment count before the packed
   2815 block (and its blobs) is uploaded, ``terms-fetched`` when it has read the
   2816 provider's ``/config`` (which is where a newly added provider's terms come
   2817 from, so a client showing them refreshes on it), and a terminal phase when the
   2818 cycle ends:
   2819 
   2820 * ``completed`` -- the cycle ran without error and without requiring payment
   2821   (``pulledBlocks`` / ``pushedBlockNonce`` carry the counters);
   2822 * ``payment-required`` -- the account is unpaid; a payment transaction may
   2823   already have been prepared, and the UI should take the user to it;
   2824 * ``error`` -- the cycle failed (with ``error`` as the reason); the wallet
   2825   retries on its own schedule, so the notification is only for the user
   2826   interface.  The reason is also persisted, and reported by ``getBackupInfo``
   2827   as the active provider's ``lastError``, so a client that was not listening
   2828   at the time still sees it.
   2829 
   2830 A cycle whose pull applied anything additionally emits a ``balance-change``
   2831 notification.  The apply path writes coins and transactions straight into the
   2832 database, so none of the transaction state machines report them; the
   2833 ``backup-status`` notification says a cycle finished, not that the wallet's
   2834 contents changed, and a client that refreshed on it alone would show a
   2835 restoring wallet as empty until something else happened.
   2836 
   2837 An earlier ``backup-error`` notification type (``BackupOperationError``) was
   2838 part of a legacy backup proof of concept and has been removed in favor of the
   2839 ``error`` phase of ``backup-status``.
   2840 
   2841 .. _limitations:
   2842 
   2843 Limitations
   2844 ===========
   2845 
   2846 While the design minimizes the metadata that the backup service is exposed to,
   2847 some leakage is inherent to the protocol and cannot be avoided in a practical
   2848 way. The service necessarily learns how many blocks and blobs an account
   2849 holds, how much data is uploaded and downloaded, and when these operations
   2850 take place. Kilobyte padding ensures that the size of an individual block or
   2851 blob reveals little about the contents it carries, but it cannot conceal the
   2852 overall volume of activity, the number of operations performed, nor their
   2853 distribution in time. In particular, the number of blocks in an account grows
   2854 with every performed operation, so the block count itself is a lower bound on
   2855 the amount of activity that cannot be disguised by padding.
   2856 
   2857 Timing patterns are particularly hard to hide. Backups run at critical points
   2858 of wallet operations and on a periodic schedule, and some of these critical
   2859 points correlate with user behavior in ways a curious service could exploit:
   2860 for example, a backup forced right before a withdrawal hints that a withdrawal
   2861 is about to occur, and one taken right after a payment hints that a payment
   2862 just happened. The frequency of periodic backups can be reduced and their
   2863 timing jittered to make such inferences harder, which also limits the amount
   2864 of metadata that accumulates over time. The backups that critical points
   2865 mandate, however, cannot be dropped without risking the loss of funds or data
   2866 and therefore remain observable. Where such behavioral patterns are
   2867 unavoidable, the user must trust the service not to misuse them -- an
   2868 assumption already made in the :ref:`threat-model`.
   2869 
   2870 Definition of done
   2871 ==================
   2872 
   2873 * [x] Design backup schema.
   2874 * [ ] Design incremental sync.
   2875 * [x] Design backup/restore schedules.
   2876 * [x] Design wallet-core API.
   2877 * [x] Wallet-core implementation.  The machinery -- block and blob encoding,
   2878   CRDT merge, the sync protocol client and its signatures, increment
   2879   collection, the scheduled backup cycle with its pull/merge/apply half, the
   2880   API request handlers, the account payment flow, and item deletion
   2881   (retro-redaction of the ``originBlocks`` plus the pull-side "deleted iff
   2882   absent from all origin blocks" sweep) -- is done, and so is **every
   2883   increment family in this document**: the exchange, global-trust,
   2884   bank-account, donau and denomination entities; the reserve family
   2885   (``set-reserve-seed`` / ``add-reserve``, with the seed-derived key pairs and
   2886   the ``reservePriv`` fallback for reserves that predate the seed), which is
   2887   what makes a restored coin recoupable; the withdrawal, deposit,
   2888   merchant-payment, peer-push-credit, peer-push-debit, peer-pull-debit and
   2889   peer-pull-credit transaction families; the refresh family, whose per-coin
   2890   session seed lets a restored wallet finish a melt instead of losing the
   2891   change; and the coin and token families, which carry the per-record key
   2892   material the wallet database stores (the seed-derived modelling of earlier
   2893   drafts is gone from both).
   2894 
   2895   Contract terms travel as blobs -- uploaded ahead of the blocks that
   2896   reference them, with their reference counts adjusted, and fetched and stored
   2897   back into the contract-terms store on the pull side; a transaction whose
   2898   terms are not available is shown in a reduced form instead of failing the
   2899   transaction listing.  Restoring a coin recomputes the coin-availability
   2900   rows, so a restored wallet shows the same balance as the wallet that made
   2901   the backup, and a restored wallet can continue a pending withdrawal (only an
   2902   expired bank operation cannot be resumed).  ``runBackupCycle`` and
   2903   ``getBackupDiagnostics``, the per-cycle statistics, the forced
   2904   full-collection pass and the ``backup-status`` notifications are all in
   2905   place, on both database backends: the native (sqlite) schema stores the
   2906   backup providers and blocks and the ``originBlocks`` of every backup-managed
   2907   record, and a wallet migrating from the IndexedDB backend carries all three
   2908   across.
   2909 
   2910   The three *derived* families -- refund, recoup and denomination loss -- are
   2911   implemented as finished facts, and every change to whether a coin counts
   2912   towards the balance (spend, refresh, recoup, denomination loss, suspend) is
   2913   reported as a coin increment, so two wallets converge on the same balance
   2914   rather than only on the same coins.  A transaction can no longer be taken
   2915   back out of a terminal state by an increment describing an older view of it.
   2916 
   2917   Known gaps, none of which loses money: refund *items* are not carried
   2918   (nothing outside the refund query reads them, and the merchant hands back
   2919   the same ones); the exchange entries and peer-pull-credit records do not
   2920   restore their ``currentMergeReserveRowId`` pointer, since it is a row id
   2921   local to one database; recoup transactions are backed up and restored but
   2922   the wallet does not yet render them as transactions; and a wallet cannot
   2923   join a sync group written by a *newer* wallet -- it refuses the blocks
   2924   rather than re-uploading a truncated view of them.
   2925 * [x] Design sync API (+ auth).
   2926 * [ ] Server-side implementation (partial: block GET/POST/PUT/DELETE, object
   2927   store GET/POST with reference counting, /config and payments done;
   2928   reconciliation mechanism still missing).
   2929 * [x] UI/UX for backup and sync, in the Android wallet: adding and removing a
   2930   provider, the account payment prompt, the recovery as a QR code and as a
   2931   paper key (written down or saved to a file) with its import counterpart,
   2932   "back up now" through ``runBackupCycle`` with a force-full-backup control
   2933   and a diagnostics card in developer mode, and a progress display driven by
   2934   the ``backup-status`` notifications.  The web extension shows the cycle in
   2935   its wallet-activity view, but has no provider management user interface yet.
   2936 
   2937 Alternatives
   2938 ============
   2939 
   2940 .. _sync-data-structures:
   2941 
   2942 Synchronization data structures
   2943 -------------------------------
   2944 
   2945 In order to perform incremental restores (i.e. synchronization) and converge
   2946 towards the global state (a.k.a. reconciliation), wallets need to keep track
   2947 (in real time) of all the changes in the backup that occurred after the last
   2948 incremental restore, resolve any resulting conflicts, and apply the changes to
   2949 the local database, all while preserving the requirements of incrementality
   2950 and plausible deniability.
   2951 
   2952 So far, two strategies to achieve this have been discussed:
   2953 
   2954 * Invertible bloom filter.
   2955 * Event-driven message queue.
   2956 
   2957 Invertible bloom filter
   2958 ~~~~~~~~~~~~~~~~~~~~~~~
   2959 
   2960 In this approach, a invertible bloom filter of dynamic size is calculated by
   2961 the wallet and server across all known blocks, and used by the wallets to
   2962 compare their local contents with the ones in the server and only fetch the
   2963 inserted and updated blocks, deleting the ones missing from the server.
   2964 
   2965 Wallets would use additional information stored in the server, such as total
   2966 number of blocks, to decide based on the number of the number of differences
   2967 with the server up to a specified threshold, whether to perform an incremental
   2968 backup using the bloom filter or simply perform a full backup.
   2969 
   2970 In order to reduce the rate of false positives, the bloom filter would be
   2971 doubled in size and recalculated as the total number of blocks increases. In
   2972 the rare event of a false positive, both the wallets and the server would
   2973 recalculate the bloom filter by adding a special prefix to the blocks before
   2974 hashing, rate-limited by the theoretical probability of false positives to
   2975 prevent denial-of-service attacks.
   2976 
   2977 Each bucket in the bloom filter (format below) would be 32 bits in size (for
   2978 optimal byte alignment) and have the following structure:
   2979 
   2980 .. code-block:: text
   2981 
   2982    +-----------------------+
   2983    | Bloom filter (10 bit) |
   2984    +-----------------------+
   2985    | Counter (4 bit)       |
   2986    +-----------------------+
   2987    | Hash (12-16 bit)      |
   2988    +-----------------------+
   2989    | Checksum (4-8 bit)    |
   2990    +-----------------------+
   2991 
   2992 Event-driven message queue
   2993 ~~~~~~~~~~~~~~~~~~~~~~~~~~
   2994 
   2995 Another proposed solution is to use a message queue used mainly to stream
   2996 blocks operations (INSERT, DELETE, UPDATE) to other wallets in the
   2997 synchronization group.
   2998 
   2999 In order to provide "eventual" plausible deniability, events in the message
   3000 queue would be permanently deleted as soon as all the active wallets in the
   3001 synchronization group have consumed them, meaning that the server would need
   3002 to keep track of all the "subscribed" wallets.
   3003 
   3004 Inactive wallets would be automatically "unsubscribed" from the message queue
   3005 after a predefined period of time (e.g. 2 weeks), or after being manually
   3006 deleted by the user (similarly to e.g. Signal). Upon coming back online or
   3007 being added back to the synchronization group, a wallet would need to perform
   3008 a full backup.
   3009 
   3010 .. TODO:
   3011    Drawbacks
   3012    =========
   3013 
   3014 Discussion / Q&A
   3015 ================
   3016 
   3017 * How to manage (add/rm) linked devices? Do they ever expire? Is there a
   3018   *master* device with permissions to manage linked devices?
   3019 
   3020 * How to safely delete a withdrawal operation? Instead of storing the keypair
   3021   for each coin, we derive coins from a secret seed and the coin index within
   3022   a withdrawal group. Coins in the backup thus contain a reference to the
   3023   originating withdrawal operation, which in the event of being deleted will
   3024   prevent coins from being restored from backup.
   3025 
   3026 * Should the wallets always keep a full copy of the linked list?