taler-docs

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

commit cd0a876306673dcf9677d4f5ae41af7e195a062e
parent cef00012044bf02dc5e165f1e53f8a03b48c58c6
Author: Iván Ávalos <avalos@disroot.org>
Date:   Fri, 31 Jul 2026 11:07:52 +0200

update DD92 with latest progress

Diffstat:
Mdesign-documents/092-incremental-backup-sync.rst | 357+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
1 file changed, 269 insertions(+), 88 deletions(-)

diff --git a/design-documents/092-incremental-backup-sync.rst b/design-documents/092-incremental-backup-sync.rst @@ -161,75 +161,259 @@ wallets that the user wishes to add to the synchronization group. | padding (to next full KB) | +----------------------------+ -.. TODO: - Block store API - +++++++++++++++ - - .. http:get:: /backups/${BACKUP_ID} - - Get backup information. - - **Response** - - .. code-block:: typescript - - interface GetBackupResponse { - /** - * Total number of blocks in the backup. - */ - total_num_blocks: number; - - /** - * First block in the backup (epoch). - */ - first_block_nonce: string; - - /** - * Current last block in the backup. - */ - last_block_nonce: string; - } - - .. http:post:: /backups/${BACKUP_ID}/block/${NONCE} - - Upload an encrypted and binary encoded block. - - **Request** - - :query prev: Optional argument providing the nonce of the previous block in - the linked list. Shall not be provided if there is no previous block. - - :query next: Optional argument providing the nonce of the next block in the - linked list. Shall not be provided if there is no previous block. - - :query blob: Optional argument providing the hash of a referenced blob. - Can be repeated once for every referenced blob. - - .. http:get:: /backups/${BACKUP_ID}/block - - Get all blocks from a backup or specific blocks. - - **Request** - - :query nonce: Optional argument providing the nonce of the block to fetch. - Can be repeated once for every block to fetch. - - .. http:put:: /backups/${BACKUP_ID}/block/${NONCE} - - Replace an existing block with a new one in-place. - - **Request** - - :query old: Nonce of the old block to replace. - - :query new: Nonce of the new block to insert. - - :query blob: Optional argument providing the hash of a referenced blob. - Can be repeated once for every referenced blob. - - .. http:delete:: /backups/${BACKUP_ID}/block/${NONCE} - - Delete an existing block from the linked list. +Block store API ++++++++++++++++ + +The account key is the base32-encoded Crockford representation of an +EdDSA public key that identifies the backup account. All upload +requests must be signed by the corresponding private key; the signature +is transmitted in the request body. + +Signatures use EdDSA with the account private key. Each signature +payload follows the common Taler signing structure with a +``purpose`` field (see :ref:`Signatures` in the API common conventions +for the general format). The specific payloads are: + +.. sourcecode:: c + + /** + * Purpose: TALER_SIGNATURE_SYNC_BLOCK_UPLOAD (1452) + * Authorizes the append or in-place update of a block. + * For appends, old_hash is all-zeros. + */ + struct SyncBlockUploadSignaturePS { + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct SYNC_BlockNonce prev_nonce; ///< all-zeros if first block + struct SYNC_BlockNonce next_nonce; ///< all-zeros if last block + struct SYNC_BlockNonce nonce; + struct GNUNET_HashCode old_hash; ///< all-zeros for appends + struct GNUNET_HashCode new_hash; + }; + + /** + * Purpose: TALER_SIGNATURE_SYNC_BLOCK_DELETE (1453) + * Authorizes the deletion of a block. + */ + struct SyncBlockDeleteSignaturePS { + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct SYNC_BlockNonce nonce; + struct SYNC_BlockNonce prev_nonce; ///< all-zeros if first block + struct SYNC_BlockNonce next_nonce; ///< all-zeros if last block + struct GNUNET_HashCode hash; + }; + + /** + * Purpose: TALER_SIGNATURE_SYNC_OBJECT_UPLOAD (1454) + * Authorizes the upload of a blob object. + */ + struct SyncObjectUploadSignaturePS { + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + struct SYNC_ObjectUID uid; + struct GNUNET_HashCode hash; + }; + +Absent optional nonces (``prev_nonce`` / ``next_nonce``) are +treated as all-zeros in the signed data. + +.. http:get:: /backups/${ACCOUNT_KEY}/blocks + + List blocks from the account's block chain with pagination. + + **Request** + + :query limit: + *Required.* Maximum number of blocks to return. Must be a positive + count (int16). + :query start_nonce: + Optional nonce of the block from which to start listing. If omitted, + listing starts from the first block. + + **Response** + + :http:statuscode:`200 OK`: + The body is a JSON array of `BlockEntry` objects. + :http:statuscode:`400 Bad request`: + The ``limit`` parameter is missing or malformed, or ``start_nonce`` + is malformed. + :http:statuscode:`402 Payment required`: + The account has expired and requires payment. + :http:statuscode:`404 Not found`: + The ``start_nonce`` block was not found in the chain. + :http:statuscode:`500 Internal server error`: + A database error occurred. + + .. ts:def:: BlockEntry + + interface BlockEntry { + nonce: string; + block_hash: string; + prev_nonce?: string; + next_nonce?: string; + data: string; + } + +.. http:post:: /backups/${ACCOUNT_KEY}/blocks/${NONCE} + + Upload a new block and append (or insert) it into the account's block + chain. If a block with the same nonce already exists, the content + hash is compared: if it matches, a ``304 Not modified`` is returned; + if it differs, the client should use ``PUT`` instead. + + The request must include an ``If-None-Match`` header containing the + quoted hex-encoded SHA-512 hash of the encrypted block data. This + hash is used by the server to detect duplicates. + + **Request** + + :query fresh: + Optional. Force the server to issue a fresh payment order even if a + pending one already exists for this account. + :query pay: + Optional. Any non-empty value (e.g. ``y``) signals that the client + wants to pay before uploading. + :query paying: + Optional. An existing order identifier. The client is promising + that it is already paying on a related order. This will cause the + server to delay processing until the respective payment has arrived + (if the operation requires a payment). Useful if the server + previously returned a ``402 Payment required`` and the client wants + to proceed as soon as the payment went through. + + The request body is a JSON object: + + .. code-block:: typescript + + interface UploadBlockRequest { + upload_sig: string; + prev_nonce?: string; + next_nonce?: string; + data: string; + object_refs?: { [uid: string]: number }; + } + + ``upload_sig`` + EdDSA signature over the block nonce, ``prev_nonce``, + ``next_nonce``, old data hash (for updates, all-zeros for appends), + and new data hash, signed with the account's private key + (``TALER_SIGNATURE_SYNC_BLOCK_UPLOAD``). + + ``prev_nonce`` + Nonce of the preceding block in the DLL. + Must be omitted for the first block. + + ``next_nonce`` + Nonce of the succeeding block in the DLL. + Must be omitted for the last block. + + ``data`` + The encrypted block contents (binary, base64-encoded). + + ``object_refs`` + Optional object whose keys are blob UIDs and whose values are + 16-bit signed integer reference-count deltas. Any objects + referenced here must have been uploaded *beforehand* via + ``POST /backups/${ACCOUNT_KEY}/objects/${UID}``. + + **Response** + + :http:statuscode:`204 No content`: + The block was stored successfully. + :http:statuscode:`304 Not modified`: + A block with the same nonce and data hash already exists. + :http:statuscode:`400 Bad request`: + Malformed parameters, bad hash, or missing required headers. + :http:statuscode:`402 Payment required`: + The account has expired and requires payment. The response includes + a ``Taler`` header with a ``taler://pay/...`` URI. + :http:statuscode:`403 Forbidden`: + The signature is invalid or does not match the request. + :http:statuscode:`409 Conflict`: + The write is outdated (the block data has been modified by another + device since the caller last fetched it). + :http:statuscode:`413 Content too large`: + The upload exceeds the server's configured upload limit. + :http:statuscode:`500 Internal server error`: + A database error occurred, or the backup is in an inconsistent + state (e.g. a referenced block is missing from the chain). + +.. http:put:: /backups/${ACCOUNT_KEY}/blocks/${NONCE} + + Replace an existing block's content in-place. Semantics are identical + to ``POST`` on the same endpoint, with one addition: the + ``If-Match`` header must contain the quoted hex-encoded SHA-512 hash + of the old block data that is being replaced. The server rejects + the request with ``409 Conflict`` if the old hash does not match. + + The ``upload_sig`` must also cover the old data hash (from + ``If-Match``) in addition to the new data hash (from + ``If-None-Match``). + + **Response** + + Same status codes as ``POST``, plus: + + :http:statuscode:`404 Not found`: + The specified block does not exist (cannot update a missing block). + +.. http:delete:: /backups/${ACCOUNT_KEY}/blocks/${NONCE} + + Delete an existing block from the block chain. The request must + include an ``If-Match`` header containing the quoted hex-encoded + SHA-512 hash of the block data to delete, which the server uses to + detect concurrent modifications. + + **Request** + + The request body is a JSON object: + + .. code-block:: typescript + + interface DeleteBlockRequest { + delete_sig: string; + prev_nonce?: string; + next_nonce?: string; + object_refs?: { [uid: string]: number }; + } + + ``delete_sig`` + EdDSA signature over the block nonce, ``prev_nonce``, + ``next_nonce``, and block hash (from ``If-Match``), signed with + the account's private key + (``TALER_SIGNATURE_SYNC_BLOCK_DELETE``). + + ``prev_nonce`` + Nonce of the preceding block in the DLL. + Must be omitted if the block being deleted is the first block. + + ``next_nonce`` + Nonce of the succeeding block in the DLL. + Must be omitted if the block being deleted is the last block. + + ``object_refs`` + Optional object whose keys are blob UIDs and whose values are + 16-bit signed integer reference-count deltas (typically negative, + to decrement the refcount of objects that were referenced by the + deleted block). + + **Response** + + :http:statuscode:`204 No content`: + The block was deleted successfully. + :http:statuscode:`400 Bad request`: + Malformed parameters or missing ``If-Match`` header. + :http:statuscode:`402 Payment required`: + The account has expired and requires payment. + :http:statuscode:`403 Forbidden`: + The signature is invalid or does not match the request. + :http:statuscode:`404 Not found`: + The specified block does not exist (or was already deleted). + :http:statuscode:`409 Conflict`: + The ``If-Match`` hash, ``prev_nonce``, or ``next_nonce`` do not + match the stored block (concurrent modification detected). + :http:statuscode:`500 Internal server error`: + A database error occurred, or the backup is in an inconsistent + state (e.g. a referenced neighbouring block is missing from the + chain, or a refcount would underflow). Hash-indexed object store ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,31 +455,27 @@ encrypted blob using SHA-512 and truncated to 32 bytes. | padding (to next full KB) | +----------------------------+ -.. TODO: - Object store API - ++++++++++++++++ - - .. http:post:: /backups/${BACKUP_ID}/object - - Upload an encrypted and binary encoded blob. +Object store API +++++++++++++++++ - .. http:get:: /backups/${BACKUP_ID}/object +.. http:get:: /backups/${ACCOUNT_KEY}/objects/${UID} - Fetch one or more existing blobs. + Retrieve an existing blob by its UID. - **Request** + **Response** - :query hash: Hash of a blob to fetch. - Should be repeated once for every blob to fetch. + :http:statuscode:`501 Not implemented`: + This operation is not yet implemented. - .. http:delete:: /backups/${BACKUP_ID}/object +.. http:post:: /backups/${ACCOUNT_KEY}/objects/${UID} - Delete an existing blob. + Upload an encrypted blob and store it in the hash-indexed object + store. The ``$UID`` is the object's unique identifier. - **Request** + **Response** - :query hash: Hash of a blob to delete. - Should be repeated once for every blob to delete. + :http:statuscode:`501 Not implemented`: + This operation is not yet implemented. .. TODO: synchronization primitive @@ -1337,8 +1517,9 @@ Definition of done * [ ] Design backup/restore schedules. * [ ] Design wallet-core API. * [ ] Wallet-core implementation. -* [ ] Design sync API (+ auth). -* [ ] Server-side implementation. +* [x] Design sync API (+ auth). +* [ ] Server-side implementation (partial: block GET/POST/PUT/DELETE, /config and + payments done; object store still stubs returning 501). * [ ] UI/UX for backup and sync. Alternatives