diff --git a/doc/architecture/index.rst b/doc/architecture/index.rst index 78fd23b431ad..04b7de64bbbb 100644 --- a/doc/architecture/index.rst +++ b/doc/architecture/index.rst @@ -57,6 +57,14 @@ Architecture Indexing system used to speed up historical queries. + --- + + :fa:`scroll` :doc:`receipts` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Receipts can be used with the ledger for audit purposes. + + --- :fa:`address-book` :doc:`tls_internals` @@ -90,6 +98,7 @@ Architecture raft_tla node_to_node indexing + receipts tls_internals tcp_internals quic_internals diff --git a/doc/architecture/receipts.rst b/doc/architecture/receipts.rst new file mode 100644 index 000000000000..1d6e3fc17d1f --- /dev/null +++ b/doc/architecture/receipts.rst @@ -0,0 +1,9 @@ +Receipts +======== + +CCF implements `write` receipts, which are signed proofs associated with a transaction. They serve two main purposes: + +1. :ref:`Endorse ` claims made by the application logic, ie. a signed statement of fact, verifiable offline and by third parties, equivalent to "this transaction produced this outcome at this position in the ledger". +2. Together with a copy of the ledger, or other receipts, they can be used to :ref:`audit ` the service and hold the consortium to account. + +Internally, receipts are also used to establish the validity of ledger snapshots. \ No newline at end of file diff --git a/doc/audit/index.rst b/doc/audit/index.rst index c68ebe37dd34..483c29051f57 100644 --- a/doc/audit/index.rst +++ b/doc/audit/index.rst @@ -3,6 +3,13 @@ Audit .. panels:: + :fa:`scroll` :doc:`receipts` + ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + Receipts can be used with the ledger for audit purposes. + + --- + :fa:`table` :doc:`builtin_maps` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -23,17 +30,9 @@ Audit --- - :fa:`scroll` :doc:`receipts` - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - Audit individual transactions on the basis of their receipt. - - --- - - .. toctree:: :hidden: + receipts builtin_maps - python_library - receipts \ No newline at end of file + python_library \ No newline at end of file diff --git a/doc/audit/receipts.rst b/doc/audit/receipts.rst index 90cc4b694c93..1413ed2b5a8e 100644 --- a/doc/audit/receipts.rst +++ b/doc/audit/receipts.rst @@ -1,60 +1,21 @@ Receipts ======== -Write Receipts --------------- +In combination with a copy of the ledger, receipts are also useful for audit purposes. -Once a transaction has been committed, it is possible to get a cryptographic receipt over the entry produced in the ledger. That receipt can be verified offline. +Check for transaction inclusion +------------------------------- -To obtain a receipt, a user needs to call a :http:GET:`/node/receipt` for a particular transaction ID. Because fetching the information necessary to produce a receipt likely involves a round trip to the ledger, the endpoint is implemented as a historical query. -This means that the request may return ``202 Accepted`` at first, with a suggested ``Retry-After`` header. A subsequent call will return the actual receipt, for example: +A user having executed a transaction, fetched a receipt for it, can check for its inclusion in the ledger. +All they need to do is scan to the corresponding :term:`Transaction ID`, digest the transaction, and compare it with `write_set_digest` in their receipt. -.. code-block:: bash +Denounce an invalid recovery +---------------------------- - $ curl -X GET "https:///app/receipt?transaction_id=2.643" --cacert service_cert.pem --key user0_privk.pem --cert user0_cert.pem +A user having executed a number of transactions, and fetched receipts for them, can denounce a recovery that removes one or more of these transactions. +This may occur if the consortium approves a catastrophic recovery from a truncated ledger. - {'cert': '-----BEGIN CERTIFICATE-----\n' - 'MIIBzjCCAVSgAwIBAgIQGR/ue9CFspRa/g6jSMHFYjAKBggqhkjOPQQDAzAWMRQw\n' - 'EgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yMjAxMjgxNjAzNDZaFw0yMjAxMjkxNjAz\n' - 'NDVaMBMxETAPBgNVBAMMCENDRiBOb2RlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE\n' - 'wsdpHLNw7xso/g71XzlQjoITiTBOef8gCayOiPJh/W2YfzreOawzD6gVQPSI+iPg\n' - 'ZPc6smFhtV5bP/WZ2KW0K9Pn+OIjm/jMU5+s3rSgts50cRjlA/k81bUI88dzQzx9\n' - 'o2owaDAJBgNVHRMEAjAAMB0GA1UdDgQWBBQgtPwYar54AQ4UL0RImVsm6wQQpzAf\n' - 'BgNVHSMEGDAWgBS2ngksRlVPvwDcLhN57VV+j2WyBTAbBgNVHREEFDAShwR/AAAB\n' - 'hwR/ZEUlhwR/AAACMAoGCCqGSM49BAMDA2gAMGUCMQDq54yS4Bmfwfcikpy2yL2+\n' - 'GFemyqNKXheFExRVt2edxVgId+uvIBGjrJEqf6zS/dsCMHVnBCLYRgxpamFkX1BF\n' - 'BDkVitfTOdYfUDWGV3MIMNdbam9BDNxG4q6XtQr4eb3jqg==\n' - '-----END CERTIFICATE-----\n', - 'leaf_components': {'commit_evidence': 'ce:2.643:55dbbbf04b71c6dcc01dd9d1c0012a6a959aef907398f7e183cc8913c82468d8', - 'write_set_digest': 'd0c521504ce2be6b4c22db8e99b14fc475b51bc91224181c75c64aa2cef72b83'}, - 'node_id': '7dfbb9a56ebe8b43c833b34cb227153ef61e4890187fe6164022255dec8f9646', - 'proof': [{'left': '00a771baf15468ed05d6ef8614b3669fcde6809314650061d64281b5d4faf9ec'}, - {'left': 'a9c8a36d01aa9dfbfb74c6f6a2cef2efcbd92bd6dfd1f7440302ad5ac7be1577'}, - {'right': '8e238d95767e6ffe4b20e1a5e93dd7b926cbd86caa83698584a16ad2dd7d60b8'}, - {'left': 'd4717996ae906cdce0ac47257a4a9445c58474c2f40811e575f804506e5fee9f'}, - {'left': 'c1c206c4670bd2adee821013695d593f5983ca0994ae74630528da5fb6642205'}], - 'signature': 'MGQCMHrnwS123oHqUKuQRPsQ+gk6WVutixeOvxcXX79InBgPOxJCoScCOlBnK4UYyLzangIwW9k7IZkMgG076qVv5zcx7OuKb7bKyii1yP1rcakeGVvVMwISeE+Fr3BnFfPD66Df'} +This user can either: -Note that receipts over signature transactions are a special case, for example: - -.. code-block:: bash - - $ curl -X GET "https:///app/receipt?transaction_id=2.35" --cacert service_cert.pem --key user0_privk.pem --cert user0_cert.pem - - {'leaf': 'fdc977c49d3a8bdf986176984e9432a09b5f6fe0c04e0b1c2dd177c03fdca9ec', - 'node_id': '06fef62c80b6471c7005c1b114166fd1b0e077845f5ad544ad4eea4fb1d31f78', - 'proof': [], - 'signature': 'MGQCMACklXqd0ge+gBS8WzewrwtwzRzSKy+bfrLZVx0YHmQvtsqs7dExYESsqrUrB8ZcKwIwS3NPKaGq0w2QlPlCqUC3vQoQvhcZgPHPu2GkFYa7JEOdSKLknNPHaCRv80zx2RGF', - 'cert': ''} - -The proof is empty, and the 'leaf' field is set to the value being signed, which is the root of the Merkle Tree covering all transactions until the signature. -This allows writing verification code that handles both regular and signature receipts similarly, but it is worth noting that the 'leaf' value for signatures is not -the digest of the signature transaction itself. - -Verifying a receipt involves the following steps: - - - Digest ``commit_evidence`` to produce ``commit_evidence_digest`` and ``claims`` to produce ``claims_digest`` when applicable. - - If the receipt contains ``leaf_components``, digest the concatenation ``write_set_digest + commit_evidence_digest + claims_digest`` to produce ``leaf``. - - Combine ``leaf`` with the successive elements in ``proof`` to calculate the value of ``root``. See :py:func:`ccf.receipt.root` for a reference implementation. - - Verify ``signature`` over the ``root`` using the certificate of the node identified by ``node_id`` and ``cert``. See :py:func:`ccf.receipt.verify` for a reference implementation. - - Check that the certificate ``cert`` of ``node_id`` used to sign the receipt is endorsed by the CCF network. See :py:func:`ccf.receipt.check_endorsement` for a reference implementation. \ No newline at end of file +1. Query the new service for receipts at the same :term:`Transaction ID` values. If those transactions come back as `INVALID`, because they were truncated, the signature over the old receipts is proof of truncation. If they come back as `COMMITTED` with a different root, the existence of two signatures over different roots at the same TxID is proof that a fork happened. +2. Scan the ledger, for example using the :doc:`/audit/python_library`, and find the transactions for which they have receipts. The `write_set_digest` in the receipt should match the digest of the transactions on disk. If it doesn't, the signature over the receipt is proof of a fork. \ No newline at end of file diff --git a/doc/build_apps/api.rst b/doc/build_apps/api.rst index 27931895b0c4..07e0b786e195 100644 --- a/doc/build_apps/api.rst +++ b/doc/build_apps/api.rst @@ -100,6 +100,13 @@ Supporting Types .. doxygenenum:: ccf::ApiResult :project: CCF +RPC Context +----------- + +.. doxygenclass:: enclave::RpcContext + :project: CCF + :members: get_request_body, get_request_query, get_request_path_params, get_request_verb, get_request_path, get_request_headers, get_request_header, get_request_url, set_claims_digest + Historical Queries ------------------ @@ -114,6 +121,10 @@ Historical Queries :project: CCF :members: +.. doxygenstruct:: ccf::TxReceipt + :project: CCF + :members: + JavaScript FFI Plugins ---------------------- diff --git a/doc/build_apps/logging_cpp.rst b/doc/build_apps/logging_cpp.rst index ea0b36e34ae3..451386a6230c 100644 --- a/doc/build_apps/logging_cpp.rst +++ b/doc/build_apps/logging_cpp.rst @@ -214,12 +214,13 @@ A user wanting to tie transaction-specific values to a receipt can do so by atta :end-before: SNIPPET_END: set_claims_digest :dedent: -CCF will then record the digest of the transaction as the combined digest of the write set, plus this claims digest. +CCF will then record this transaction as a leaf in the Merkle tree constructed from the combined digest of the write set, this ``claims_digest``, and the commit evidence. -Receipts for transactions that have set a claims digest expose a ``leaf_components``, rather than an opaque ``leaf``, -which means that a receipt endpoint can choose to reveal the claims and remove their digest from the receipt. +This ``claims_digest`` will be exposed in receipts under ``leaf_components``. It can then be revealed externally, +or by the endpoint directly if it has been stored in the ledger. The receipt object deliberately makes the ``claims_digest`` optional, +to allow the endpoint to remove it when the claims themselves are revealed. -The receipt verification can then only succeed if the revealed claims are digested and their digest combined into a +Receipt verification can then only succeed if the revealed claims are digested and their digest combined into a ``leaf`` that correctly combines with the ``proof`` to form the ``root`` that the signature covers. Receipt verification therefore establishes the authenticity of the claims. @@ -229,5 +230,48 @@ therefore establishes the authenticity of the claims. :end-before: SNIPPET_END: claims_digest_in_receipt :dedent: -A client consuming the output of this endpoint can then digest the claims themselves, combine the digest with the other leaf component -(``write_set_digest``) to obtain the equivalent ``leaf``. \ No newline at end of file +A client consuming the output of this endpoint must digest the claims themselves, combine the digest with the other leaf components +(``write_set_digest`` and ``hash(commit_evidence)``) to obtain the equivalent ``leaf``. See :ref:`use_apps/verify_tx:Receipt Verification` for the full set of steps. + +As an example, a logging application may register the contents being logged as a claim: + +.. literalinclude:: ../../samples/apps/logging/logging.cpp + :language: cpp + :start-after: SNIPPET_START: record_public + :end-before: SNIPPET_END: record_public + :dedent: + +And expose an endpoint returning receipts, with that claim expanded: + +.. literalinclude:: ../../samples/apps/logging/logging.cpp + :language: cpp + :start-after: SNIPPET_START: get_historical_with_receipt + :end-before: SNIPPET_END: get_historical_with_receipt + :dedent: + +Receipts from this endpoint will then look like: + +.. code-block:: json + + {'msg': 'Public message at idx 5 [0]', + 'receipt': {'cert': '-----BEGIN CERTIFICATE-----\n' + 'MIIBzzCCAVWgAwIBAgIRANKoegKBViucMxSPzftnDB4wCgYIKoZIzj0EAwMwFjEU\n' + 'MBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjIwMzE1MjExODIwWhcNMjIwMzE2MjEx\n' + 'ODE5WjATMREwDwYDVQQDDAhDQ0YgTm9kZTB2MBAGByqGSM49AgEGBSuBBAAiA2IA\n' + 'BG+RJ5qNPOga8shCF3w64yija/ShW46JxrE0n9kDybyRf+L3810GjCvjxSpzTQhX\n' + '5WEF2dou1dG2ppI/KSNQsSfk081lbaB50NADWw+jDCtrq/fKuZ+w9wQSaoSvE5+0\n' + '1qNqMGgwCQYDVR0TBAIwADAdBgNVHQ4EFgQU7tFQR91U1EDhup1XPS3u0w5+R2Yw\n' + 'HwYDVR0jBBgwFoAU3aI0vfJMBdWckvv9dKK2UzNCLU0wGwYDVR0RBBQwEocEfwAA\n' + 'AYcEfxoNCocEfwAAAjAKBggqhkjOPQQDAwNoADBlAjAiOmvGpatg4Uq8phQkwj/p\n' + 'Wj33fih6SUtRHOpdsIKvbV8TDNHRdSo1RKPArDd1w1wCMQDnw9zziS5G8qwvucP3\n' + 'gn3htz+2ZPBJRr98AqmRNmgflhgqLQp+jAVPrJaWtD3fDpw=\n' + '-----END CERTIFICATE-----\n', + 'leaf_components': {'commit_evidence': 'ce:2.25:54571ec6d0540b364d8343b74dff055932981fd72a24c1399c39ca9c74d2f713', + 'write_set_digest': '08b044fc5b0e9cd03c68d77c949bb815e3d70bd24ad339519df48758430ac0f7'}, + 'node_id': '95baf92969b4c9e52b4f8fcde830dea9fa0286a8c3a92cda4cffcf8251c06b39', + 'proof': [{'left': '50a1a35a50bd2c5a4725907e77f3b1f96f1f9f37482aa18f8e7292e0542d9d23'}, + {'left': 'e2184154ac72b304639b923b3c7a0bc04cecbd305de4f103a174a90210cae0dc'}, + {'left': 'abc9bcbeff670930c34ebdab0f2d57b56e9d393e4dccdccf2db59b5e34507422'}], + 'signature': 'MGUCMHYBgZ3gySdkJ+STUL13EURVBd8354ULC11l/kjx20IwpXrg/aDYLWYf7tsGwqUxPwIxAMH2wJDd9wpwbQrULpaAx5XEifpUfOriKtYo7XiFr05J+BV10U39xa9GBS49OK47QA=='}} + +Note that the ``claims_digest`` is not present in the ``leaf_components``, and must be re-computed by digesting the ``msg``. \ No newline at end of file diff --git a/doc/overview/glossary.rst b/doc/overview/glossary.rst index 68062520fefd..bf49b69dc492 100644 --- a/doc/overview/glossary.rst +++ b/doc/overview/glossary.rst @@ -80,5 +80,8 @@ Glossary TLS `Transport Layer Security `_ is an IETF cryptographic protocol standard designed to secure communications between a client and a server over a computer network. + Transaction ID + Unique transaction identifier in CCF, composed of a View and a Sequence Number. Sequence Numbers start from 1, and are contiguous. Views are monotonic. + Users Directly interact with the application running in CCF. Their public identity should be voted in by members before they are allowed to issue requests. diff --git a/doc/use_apps/verify_tx.rst b/doc/use_apps/verify_tx.rst index 5b0cfe896246..4b202058f969 100644 --- a/doc/use_apps/verify_tx.rst +++ b/doc/use_apps/verify_tx.rst @@ -20,7 +20,7 @@ To guarantee that their request is successfully committed to the ledger, a user {"status":"COMMITTED"} -This example queries the status of transaction ID ``2.18`` (constructed from view ``2`` and sequence number ``18``). The response indicates this was successfully committed. The headers also show that the service has since made progress with other requests and changed view (``x-ms-ccf-transaction-id: 5.42``). +This example queries the status of :term:`Transaction ID` ``2.18`` (constructed from view ``2`` and sequence number ``18``). The response indicates this was successfully committed. The headers also show that the service has since made progress with other requests and changed view (``x-ms-ccf-transaction-id: 5.42``). The possible statuses returned by :http:GET:`/app/tx` are: @@ -43,6 +43,100 @@ It is possible that intermediate states are not visible (e.g. a transition from Note that transaction IDs are uniquely assigned by the service - once a request has been assigned an ID, this ID will never be associated with a different write transaction. In normal operation, the next requests will be given versions 2.19, then 2.20, and so on, and after a short delay ``2.18`` will be committed. If requests are submitted in parallel, they will be applied in a consistent order indicated by their assigned versions. -If the network is unable to reach consensus, it will trigger a leadership election which increments the view. In this case the user's next request may be given a version ``3.16``, followed by ``3.17``, then ``3.18``. The sequence number is reused, but in a different view; the service knows that ``2.18`` can never be assigned, so it can report this as an invalid ID. Read-only transactions are an exception - they do not get a unique transaction ID but instead return the ID of the last write transaction whose state they may have read. +If the network is unable to reach consensus, it will trigger a leadership election which increments the view. In this case the user's next request may be given a version ``3.16``, followed by ``3.17``, then ``3.18``. The sequence number is reused, but in a different view; the service knows that ``2.18`` can never be assigned, so it can report this as an invalid ID. Read-only transactions are an exception - they do not get a unique :term:`Transaction ID` but instead return the ID of the last write transaction whose state they may have read. -.. note:: To verify a transaction offline, see :ref:`receipts `. +Write Receipts +-------------- + +Once a transaction has been committed, it is possible to get a cryptographic receipt over the entry produced in the ledger. That receipt can be verified offline. + +To obtain a receipt, a user needs to call a :http:GET:`/app/receipt` for a particular :term:`Transaction ID`. Because fetching the information necessary to produce a receipt likely involves a round trip to the ledger, the endpoint is implemented as a historical query. +This means that the request may return ``202 Accepted`` at first, with a suggested ``Retry-After`` header. A subsequent call will return the actual receipt, for example: + +.. code-block:: bash + + $ curl -X GET "https:///app/receipt?transaction_id=2.643" --cacert service_cert.pem --key user0_privk.pem --cert user0_cert.pem + + {'cert': '-----BEGIN CERTIFICATE-----\n' + 'MIIBzjCCAVSgAwIBAgIQGR/ue9CFspRa/g6jSMHFYjAKBggqhkjOPQQDAzAWMRQw\n' + 'EgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yMjAxMjgxNjAzNDZaFw0yMjAxMjkxNjAz\n' + 'NDVaMBMxETAPBgNVBAMMCENDRiBOb2RlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE\n' + 'wsdpHLNw7xso/g71XzlQjoITiTBOef8gCayOiPJh/W2YfzreOawzD6gVQPSI+iPg\n' + 'ZPc6smFhtV5bP/WZ2KW0K9Pn+OIjm/jMU5+s3rSgts50cRjlA/k81bUI88dzQzx9\n' + 'o2owaDAJBgNVHRMEAjAAMB0GA1UdDgQWBBQgtPwYar54AQ4UL0RImVsm6wQQpzAf\n' + 'BgNVHSMEGDAWgBS2ngksRlVPvwDcLhN57VV+j2WyBTAbBgNVHREEFDAShwR/AAAB\n' + 'hwR/ZEUlhwR/AAACMAoGCCqGSM49BAMDA2gAMGUCMQDq54yS4Bmfwfcikpy2yL2+\n' + 'GFemyqNKXheFExRVt2edxVgId+uvIBGjrJEqf6zS/dsCMHVnBCLYRgxpamFkX1BF\n' + 'BDkVitfTOdYfUDWGV3MIMNdbam9BDNxG4q6XtQr4eb3jqg==\n' + '-----END CERTIFICATE-----\n', + 'leaf_components': {'commit_evidence': 'ce:2.643:55dbbbf04b71c6dcc01dd9d1c0012a6a959aef907398f7e183cc8913c82468d8', + 'write_set_digest': 'd0c521504ce2be6b4c22db8e99b14fc475b51bc91224181c75c64aa2cef72b83'}, + 'node_id': '7dfbb9a56ebe8b43c833b34cb227153ef61e4890187fe6164022255dec8f9646', + 'proof': [{'left': '00a771baf15468ed05d6ef8614b3669fcde6809314650061d64281b5d4faf9ec'}, + {'left': 'a9c8a36d01aa9dfbfb74c6f6a2cef2efcbd92bd6dfd1f7440302ad5ac7be1577'}, + {'right': '8e238d95767e6ffe4b20e1a5e93dd7b926cbd86caa83698584a16ad2dd7d60b8'}, + {'left': 'd4717996ae906cdce0ac47257a4a9445c58474c2f40811e575f804506e5fee9f'}, + {'left': 'c1c206c4670bd2adee821013695d593f5983ca0994ae74630528da5fb6642205'}], + 'signature': 'MGQCMHrnwS123oHqUKuQRPsQ+gk6WVutixeOvxcXX79InBgPOxJCoScCOlBnK4UYyLzangIwW9k7IZkMgG076qVv5zcx7OuKb7bKyii1yP1rcakeGVvVMwISeE+Fr3BnFfPD66Df'} + +`cert` contains the certificate of the signing node, endorsed by the service identity. `node_id` is the node's ID inside CCF, a digest of its public key. + +Note that receipts over signature transactions are a special case, for example: + +.. code-block:: bash + + $ curl -X GET "https:///app/receipt?transaction_id=2.35" --cacert service_cert.pem --key user0_privk.pem --cert user0_cert.pem + + {'leaf': 'fdc977c49d3a8bdf986176984e9432a09b5f6fe0c04e0b1c2dd177c03fdca9ec', + 'node_id': '06fef62c80b6471c7005c1b114166fd1b0e077845f5ad544ad4eea4fb1d31f78', + 'proof': [], + 'signature': 'MGQCMACklXqd0ge+gBS8WzewrwtwzRzSKy+bfrLZVx0YHmQvtsqs7dExYESsqrUrB8ZcKwIwS3NPKaGq0w2QlPlCqUC3vQoQvhcZgPHPu2GkFYa7JEOdSKLknNPHaCRv80zx2RGF', + 'cert': ''} + +The proof is empty, and the ``leaf`` field is set to the value being signed, which is the root of the Merkle Tree covering all transactions until the signature. +This allows writing verification code that handles both regular and signature receipts similarly, but it is worth noting that the 'leaf' value for signatures is _not_ +the digest of the signature transaction itself. + +Receipt Verification +-------------------- + +Verifying a receipt consists of the following steps: + + 1. Digest ``commit_evidence`` to produce ``commit_evidence_digest`` and ``claims`` to produce ``claims_digest`` when applicable. + 2. If the receipt contains ``leaf_components``, digest the concatenation ``write_set_digest + commit_evidence_digest + claims_digest`` to produce ``leaf``. + 3. Combine ``leaf`` with the successive elements in ``proof`` to calculate the value of ``root``. See :py:func:`ccf.receipt.root` for a reference implementation. + 4. Verify ``signature`` over the ``root`` using the certificate of the node identified by ``node_id`` and ``cert``. See :py:func:`ccf.receipt.verify` for a reference implementation. + 5. Check that the certificate ``cert`` of ``node_id`` used to sign the receipt is endorsed by the CCF network. See :py:func:`ccf.receipt.check_endorsement` for a reference implementation. + +Note that since a receipt is a committment by a service to a transaction, a verifier must know the service identity, and provide it as an input to step 5. + +Application Claims +------------------ + +CCF allows application code to attach arbitrary claims to a transaction, via the :cpp:func:`enclave::RpcContext::set_claims_digest` API, as illustrated in :ref:`build_apps/logging_cpp:User-Defined Claims in Receipts`. + +This is useful to allow the reveal and verification of application-related claims offline, ie. without access to the CCF network. +For example, a logging application may choose to set the digest of the payload being logged as `claims_digest`. +A user who logs a payload can then present the receipt and the payload to a third party, who can confirm that they match, having verified the receipt. They can perform this verification without access to the service. + +Multiple claims can be registered by storing them in a collection or object whose digest is set as `claims_digest`. It is possible to reveal them selectively, by capturing their digest in turn, rather than their raw value directly, eg: + +`claims_digest = hash( hash(claim_a) + hash(claim_b) )` + +Revealing `hash(claim_a)` and `claim_b` allows verification without revealing `claim_a` in this case. + +Although CCF takes the approach of concatenating leaf components to keep its implementation simple and format-agnostic, an application may choose to encode its claims in a structured way for convenience, for example as JSON, CBOR etc. + +Applications may wish to expose dedicated endpoints, besides CCF's built-in :http:GET:`/node/receipt`, in which they can selectively expand claims, as illustrated in :ref:`build_apps/logging_cpp:User-Defined Claims in Receipts`. +If some claims must stay confidential, applications should encrypt them rather than merely digest them. They key can be kept in a private table for example, which like the claim will be available through the historical query API. The application logic can then decide whether to decrypt the claim for the caller depending on its authorisation policy. + +Commit Evidence +--------------- + +The `commit_evidence` field in receipts fulfills two purposes: + +1. It exposes the full :term:`Transaction ID` in a format that is easy for a user to extract, and does not require parsing the ledger entry. +2. Because it cannot be extracted from the ledger without access to the ledger secrets, it guarantees the transaction is committed. + +Entries are written out to the ledger as early as possible, to relieve memory pressure inside the enclave. If receipts could be produced from these entries regardless of their replication status, a malicious actor could emit them for transactions that have been tentatively run by a primary, appended to its local ledger, but since rolled back. +By including a committment to the digest of `commit_evidence` as a leaf component in the Merkle Tree, which is effectively a nonce derived from ledger secrets and TxID, we ensure that only receipts produced by nodes that can reveal this nonce are verifiable. \ No newline at end of file diff --git a/include/ccf/receipt.h b/include/ccf/receipt.h index e001cd680ee7..3379513e5d53 100644 --- a/include/ccf/receipt.h +++ b/include/ccf/receipt.h @@ -35,13 +35,21 @@ namespace ccf bool operator==(const LeafComponents& other) const = default; }; + /// Signature over the root of the Merkle Tree, by the node private key std::string signature; + /// Root of the Merkle Tree std::optional root = std::nullopt; + /// Merkle proof from the signed root to the leaf components std::vector proof = {}; + /// Node identity that produced the signature at the time ccf::NodeId node_id; + /// Node identity as a PEM certificate std::optional cert = std::nullopt; // In practice, either leaf or leaf_components is set + /// Leaf of the Merkle proof, only set on transactions emitted by 1.x + /// networks. Corresponds to the write set digest. std::optional leaf = std::nullopt; + /// Leaf components in transactions emitted by 2.x networks. std::optional leaf_components = std::nullopt; }; diff --git a/include/ccf/rpc_context.h b/include/ccf/rpc_context.h index aadc4497cd32..86f81ec4cf59 100644 --- a/include/ccf/rpc_context.h +++ b/include/ccf/rpc_context.h @@ -151,6 +151,7 @@ namespace ccf /// transaction. This allows a transaction to make specific, /// separately-revealable claims in each transaction, without being bound to /// the transaction serialisation format or what is stored in the KV. + /// The digest will be included in receipts issued for that transaction. virtual void set_claims_digest(ccf::ClaimsDigest::Digest&& digest) = 0; ///@} };