diff --git a/CHANGELOG.md b/CHANGELOG.md index cfab6965a6f..d43c58efc29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Added `ccf::describe_cose_receipt_v1(receipt)` to obtain COSE receipts with Merkle proof in unprotected header for non-signature TXs, and empty unprotected header for signature TXs (#7700). - `NetworkIdentitySubsystemInterface` now exposes `get_trusted_keys()`, returning all trusted network identity keys as a `TrustedKeys` map (#7690). ### Changed diff --git a/include/ccf/receipt.h b/include/ccf/receipt.h index 267301e087b..8ef4f601c11 100644 --- a/include/ccf/receipt.h +++ b/include/ccf/receipt.h @@ -156,10 +156,13 @@ namespace ccf using SerialisedCoseEndorsement = std::vector; using SerialisedCoseSignature = std::vector; using SerialisedCoseEndorsements = std::vector; + using SerialisedCoseReceipt = std::vector; std::optional describe_cose_endorsements_v1( const TxReceiptImpl& receipt); std::optional describe_cose_signature_v1( const TxReceiptImpl& receipt); + std::optional describe_cose_receipt_v1( + const TxReceiptImpl& receipt); // Manual JSON serializers are specified for these types as they are not // trivial POD structs diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 3be134eedbc..f548ac090d3 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -2178,41 +2178,23 @@ namespace loggingapp auto get_cose_receipt = []( ccf::endpoints::ReadOnlyEndpointContext& ctx, ccf::historical::StatePtr historical_state) { - auto historical_tx = historical_state->store->create_read_only_tx(); - assert(historical_state->receipt); - auto signature = describe_cose_signature_v1(*historical_state->receipt); - if (!signature.has_value()) - { - ctx.rpc_ctx->set_error( - HTTP_STATUS_NOT_FOUND, - ccf::errors::ResourceNotFound, - "No COSE signature available for this transaction"); - return; - } - auto proof = describe_merkle_proof_v1(*historical_state->receipt); - if (!proof.has_value()) + auto cose_receipt = + describe_cose_receipt_v1(*historical_state->receipt); + if (!cose_receipt.has_value()) { ctx.rpc_ctx->set_error( HTTP_STATUS_NOT_FOUND, ccf::errors::ResourceNotFound, - "No merkle proof available for this transaction"); + "No COSE receipt available for this transaction"); return; } - constexpr int64_t vdp = 396; - auto inclusion_proof = ccf::cose::edit::pos::AtKey{-1}; - - ccf::cose::edit::desc::Value desc{inclusion_proof, vdp, *proof}; - - auto cose_receipt = - ccf::cose::edit::set_unprotected_header(*signature, desc); - ctx.rpc_ctx->set_response_status(HTTP_STATUS_OK); ctx.rpc_ctx->set_response_header( ccf::http::headers::CONTENT_TYPE, ccf::http::headervalues::contenttype::COSE); - ctx.rpc_ctx->set_response_body(cose_receipt); + ctx.rpc_ctx->set_response_body(*cose_receipt); }; make_read_only_endpoint( "/log/public/cose_receipt", diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index a4dc4acc70d..91be49da942 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -3,10 +3,12 @@ #include "ccf/historical_queries_adapter.h" +#include "ccf/crypto/cose.h" #include "ccf/historical_queries_utils.h" #include "ccf/rpc_context.h" #include "ccf/service/tables/service.h" #include "crypto/cbor.h" +#include "crypto/cose.h" #include "kv/kv_types.h" #include "node/rpc/network_identity_subsystem.h" #include "node/tx_receipt_impl.h" @@ -253,6 +255,30 @@ namespace ccf { return receipt.cose_signature; } + + std::optional describe_cose_receipt_v1( + const TxReceiptImpl& receipt) + { + auto signature = describe_cose_signature_v1(receipt); + if (!signature.has_value()) + { + return std::nullopt; + } + + auto proof = describe_merkle_proof_v1(receipt); + if (!proof.has_value()) + { + // Signature TX: return COSE signature as-is, with empty UHDR + return signature; + } + + auto inclusion_proof = + ccf::cose::edit::pos::AtKey{ccf::cose::header::iana::INCLUSION_PROOFS}; + ccf::cose::edit::desc::Value desc{ + inclusion_proof, ccf::cose::header::iana::VDP, *proof}; + + return ccf::cose::edit::set_unprotected_header(*signature, desc); + } } namespace ccf::historical diff --git a/tests/e2e_logging.py b/tests/e2e_logging.py index 35c149c676e..d45b52efb76 100644 --- a/tests/e2e_logging.py +++ b/tests/e2e_logging.py @@ -910,10 +910,17 @@ def test_cbor_receipts(network, args): log_capture=[], # Do not emit raw binary to stdout ) if r.status_code == http.HTTPStatus.OK: - found_receipt = True cose_receipt = r.body.data() uhdr = cbor2.loads(cose_receipt).value[1] - proofs = uhdr[396][-1] + VDP_KEY = 396 + if VDP_KEY not in uhdr: + # Signature TX: valid receipt with empty UHDR, skip to next seqno + LOG.debug( + f"Transaction {txid} is a signature TX (empty UHDR), skipping" + ) + break + found_receipt = True + proofs = uhdr[VDP_KEY][-1] assert len(proofs) > 0, "No Merkle proofs found in receipt" r = client.get(