Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

### Added

- Added `ccf::describe_cose_receipt(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
Expand Down
3 changes: 3 additions & 0 deletions include/ccf/receipt.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,10 +156,13 @@ namespace ccf
using SerialisedCoseEndorsement = std::vector<uint8_t>;
using SerialisedCoseSignature = std::vector<uint8_t>;
using SerialisedCoseEndorsements = std::vector<SerialisedCoseEndorsement>;
using SerialisedCoseReceipt = std::vector<uint8_t>;
std::optional<SerialisedCoseEndorsements> describe_cose_endorsements_v1(
const TxReceiptImpl& receipt);
std::optional<SerialisedCoseSignature> describe_cose_signature_v1(
const TxReceiptImpl& receipt);
std::optional<SerialisedCoseReceipt> describe_cose_receipt(
const TxReceiptImpl& receipt);

// Manual JSON serializers are specified for these types as they are not
// trivial POD structs
Expand Down
28 changes: 5 additions & 23 deletions samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(*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",
Expand Down
26 changes: 26 additions & 0 deletions src/node/historical_queries_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -253,6 +255,30 @@ namespace ccf
{
return receipt.cose_signature;
}

std::optional<SerialisedCoseReceipt> describe_cose_receipt(
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
Expand Down
11 changes: 9 additions & 2 deletions tests/e2e_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 # ccf::cose::header::iana::VDP
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(
Expand Down