Issuing Commands

Clients communicate with CCF using HTTP requests, over TLS.

For example, to record a message at a specific id with the C++ sample logging application using curl:

$ cat request.json
  "id": 42,
  "msg": "Hello There"

$ curl https://<ccf-node-address>/app/log/private --cacert service_cert.pem --key user0_privk.pem --cert user0_cert.pem --data-binary @request.json -H "content-type: application/json" -i
HTTP/1.1 200 OK
content-length: 5
content-type: application/json
x-ms-ccf-transaction-id: 2.23


The HTTP response some CCF commit information in the headers:

  • "x-ms-ccf-transaction-id" indicates the consensus view, and the unique version at which the request was executed, separated by a ".".

The response body (the JSON value true) indicates that the request was executed successfully. For many RPCs this will be a JSON object with more details about the execution result.


In some situations CCF requires signed requests, for example for member votes. Only one signing scheme is supported as of 4.x:

COSE Sign1

CCF accepts signed requests in COSE Sign1 format.

CCF identifies the signing identity for a request via the SHA-256 digest of its certificate, represented as a hex string. That value must be set in the kid protected header. Additional protected headers may be necessary, for example governance endpoints require setting,, and optionally on the message types where it applies.

A signing script (ccf_cose_sign1) is provided as part of the ccf Python package. The output can be piped directly into curl, or any other HTTP client.

Commands can also be signed using the pycose library, and sent with any standard HTTP library such as Python HTTPX.


To make governance commands idempotent, and to prevent potential replay attacks where old signed requests are submitted again, a creation timestamp must be set in the protected header parameter.

A fixed-sized window of proposal request digests is kept by CCF, and newly submitted proposal requests must not collide with existing entries nor be older than the median proposal request in the window. The size of the window is defined in service.config.

The timestamp must be submitted as a integer number of seconds since Unix epoch (Thursday 1 January 1970 00:00:00 UT).


HTTP request signing could be used in previous versions of CCF, but has been removed as of 4.0, in favour of COSE Sign1.

COSE Schemas

Each endpoint which requires a COSE signed request requires certain protected headers to be included, and a specific fields to be present in the JSON payload body. These requirements are listed below.


Creating a new proposal:


POST /gov/members/proposals:create

Protected headers = proposal = <creation timestamp>


{ “actions”: […] }
See Creating a Proposal for details

Withdrawing a proposal:


POST /gov/members/proposals/{proposalId}:withdraw

Protected headers = withdrawal = <creation timestamp> = <proposalId>



Submitting a ballot:


POST /gov/members/proposals/{proposalId}/ballots/{memberId}:submit

Protected headers = ballot = <creation timestamp> = <proposalId>


{ “ballot”: “…” }
See Creating a Ballot for details

Updating state digest:


POST /gov/members/state-digests/{memberId}:update

Protected headers = state_digest = <creation timestamp>



Acking state digest:


POST /gov/members/state-digests/{memberId}:ack

Protected headers = ack = <creation timestamp>


{ “stateDigest”: “<hex digest>” }
This should be the object returned by a previous call to GET /gov/members/state-digests/{memberId}

Submitting recovery share:


POST /gov/recovery/members/{memberId}:recover

Protected headers = encrypted_recovery_share = <creation timestamp>


{ “share”: “<base64-encoded decrypted share>” }