Opening a Network

This section assumes that a set of nodes has already been started by Operators. See Running a CCF Service.

Adding Users

Note

The ccf_cose_sign1 script is distributed in the ccf Python package, available on PyPI. It can be installed with pip install ccf.

Once a CCF network is successfully started and an acceptable number of nodes have joined, members should vote to open the network to Users. First, the identities of trusted users should be generated, see Generating Member Keys and Certificates.

Then, the certificates of trusted users should be registered in CCF via the member governance interface. For example, the first member may decide to make a proposal to add a new user (here, cert is the PEM certificate of the user – see Cryptography for a list of supported algorithms):

$ cat set_user.json
{
    "actions": [
        {
            "name": "set_user",
            "args": {
                "cert": "-----BEGIN CERTIFICATE-----\nMIIBs...<SNIP>...yR\n-----END CERTIFICATE-----\n"
            }
        }
    ]
}
$ ccf_cose_sign1 \
  --ccf-gov-msg-type proposal \
  --ccf-gov-msg-created_at `date -uIs` \
  --signing-key member0_privk.pem \
  --signing-cert member0_cert.pem \
  --content set_user.json \
| curl https://<ccf-node-address>/gov/members/proposals:create?api-version=2024-07-01 \
  --cacert service_cert.pem \
  --data-binary @- \
  -H "content-type: application/cose"
{
    "ballotCount": 0,
    "proposalId": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253",
    "proposerId": "2af6cb6c0af07818186f7ef7151061174c3cb74b4a4c30a04a434f0c2b00a8c0",
    "proposalState": "Open"
}

Other members are then allowed to vote for the proposal, using the proposal id returned to the proposer member. They may submit an unconditional approval, or their vote may query the current state and the proposed actions. These votes must be signed.

$ cat vote_accept.json
{
    "ballot": "export function vote (proposal, proposerId) { return true }"
}
$ ccf_cose_sign1 \
  --ccf-gov-msg-type ballot \
  --ccf-gov-msg-created_at `date -uIs` \
  --ccf-gov-msg-proposal_id f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253 \
  --signing-key member0_privk.pem \
  --signing-cert member0_cert.pem \
  --content vote_accept.json \
| curl https://<ccf-node-address>/gov/members/proposals/f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253/ballots/2af6cb6c0af07818186f7ef7151061174c3cb74b4a4c30a04a434f0c2b00a8c0:submit?api-version=2024-07-01 \
  --cacert service_cert.pem \
  --data-binary @- \
  -H "content-type: application/cose"
{
    "ballotCount": 1,
    "proposalId": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253",
    "proposerId": "2af6cb6c0af07818186f7ef7151061174c3cb74b4a4c30a04a434f0c2b00a8c0",
    "proposalState": "Open"
}
$ ccf_cose_sign1 \
  --ccf-gov-msg-type ballot \
  --ccf-gov-msg-created_at `date -uIs` \
  --ccf-gov-msg-proposal_id f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253 \
  --signing-key member1_privk.pem \
  --signing-cert member1_cert.pem \
  --content vote_accept.json \
| curl https://<ccf-node-address>/gov/members/proposals/f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253/ballots/75b86775f1253c308f4e9aeddf912d40b8d77db9eaa9a0f0026f581920d5e9b8:submit?api-version=2024-07-01 \
  --cacert service_cert.pem \
  --data-binary @- \
  -H "content-type: application/cose"
{
    "ballotCount": 2,
    "proposalId": "f665047e3d1eb184a7b7921944a8ab543cfff117aab5b6358dc87f9e70278253",
    "proposerId": "2af6cb6c0af07818186f7ef7151061174c3cb74b4a4c30a04a434f0c2b00a8c0",
    "proposalState": "Open"
}

The user is successfully added once the proposal has received enough votes under the rules of the Constitution (indicated by the response body showing a transition to state Accepted).

The user can then make user RPCs.

User Data

For each user, CCF also stores arbitrary user-data in a JSON object. This can only be written to by members, subject to the standard proposal-vote governance mechanism, via the set_user_data action. This lets members define initial metadata for certain users; for example to grant specific privileges, associate a human-readable name, or categorise the users. This user-data can then be read (but not written) by user-facing endpoints.

For example, the /log/private/admin_only endpoint in the C++ logging sample app uses user-data to restrict who is permitted to call it:

// Check caller's user-data for required permissions
nlohmann::json user_data = nullptr;
auto result = get_user_data_v1(ctx.tx, caller_ident.user_id, user_data);
if (result == ccf::ApiResult::InternalError)
{
  return ccf::make_error(
    HTTP_STATUS_INTERNAL_SERVER_ERROR,
    ccf::errors::InternalError,
    fmt::format(
      "Failed to get user data for user {}: {}",
      caller_ident.user_id,
      ccf::api_result_to_str(result)));
}
const auto is_admin_it = user_data.find("isAdmin");

// Exit if this user has no user data, or the user data is not an
// object with isAdmin field, or the value of this field is not true
if (
  !user_data.is_object() || is_admin_it == user_data.end() ||
  !is_admin_it.value().get<bool>())
{
  return ccf::make_error(
    HTTP_STATUS_FORBIDDEN,
    ccf::errors::AuthorizationFailed,
    "Only admins may access this endpoint.");
}

Members configure this permission with set_user_data proposals:

$ cat set_user_data_proposal.json
{
    "actions": [
        {
            "name": "set_user_data",
            "args": {
                "user_id": "529d0f48287923e7536a708c0b7747666f6b904d3fd4b84739f7d2204233a16e",
                "user_data": {
                    "isAdmin": true
                }
            }
        }
    ]
}

Once this proposal is accepted, the newly added user (with ID 529d0f48287923e7536a708c0b7747666f6b904d3fd4b84739f7d2204233a16e) is able to use this endpoint:

$ curl https://<ccf-node-address>/app/log/private/admin_only --key user0_privk.pem --cert user0_cert.pem --cacert service_cert.pem -X POST --data-binary '{"id": 42, "msg": "hello world"}' -H "Content-type: application/json" -i
HTTP/1.1 200 OK

true

All other users have empty or non-matching user-data, so will receive a HTTP error if they attempt to access it:

$ curl https://<ccf-node-address>/app/log/private/admin_only --key user1_privk.pem --cert user1_cert.pem --cacert service_cert.pem -X POST --data-binary '{"id": 42, "msg": "hello world"}' -H "Content-type: application/json" -i
HTTP/1.1 403 Forbidden

{"error":{"code":"AuthorizationFailed","message":"Only admins may access this endpoint."}}

Opening the Network

Once users are added to the opening network, members should create a proposal to open the network:

$ cat transition_service_to_open.json
{
    "actions": [
        {
            "name": "transition_service_to_open",
            "args": {
                "next_service_identity": "-----BEGIN CERTIFICATE-----\nMIIBezCCASGgAwIBAgIRAOVHYf9qhvjzdoIw3fPHp5YwCgYIKoZIzj0EAwIwFjEU\nMBIGA1UEAwwLQ0NGIE5ldHdvcmswHhcNMjIwMzExMTcwNTQzWhcNMjIwMzEyMTcw\nNTQyWjAWMRQwEgYDVQQDDAtDQ0YgTmV0d29yazBZMBMGByqGSM49AgEGCCqGSM49\nAwEHA0IABBZXMHCrjfBeO+FHqDG8Szjzc4lQC8KmvTX8Il0ZERXH/mjLZ7Dc52rX\nnilD1ghdRDWXiKMQWT9RPvm4tefWHD6jUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0O\nBBYEFCUmm9u05D0/IFupggFW5VgVlUSyMB8GA1UdIwQYMBaAFCUmm9u05D0/IFup\nggFW5VgVlUSyMAoGCCqGSM49BAMCA0gAMEUCIQCy6WoeLtTUD8GRIOM+oRNe/lTj\nRrrry+0AxZgxBU1oSwIgJmyrTfT90re+rzAkF9uiqoL44TVWkQf1t3cZrgVFYK8=\n-----END CERTIFICATE-----\n"
            }
        }
    ]
}
$ ccf_cose_sign1 \
  --ccf-gov-msg-type proposal \
  --ccf-gov-msg-created_at `date -uIs` \
  --signing-key member0_privk.pem \
  --signing-cert member0_cert.pem \
  --content transition_service_to_open.json
| curl https://<ccf-node-address>/gov/members/proposals:create?api-version=2024-07-01 \
  --cacert service_cert.pem \
  --data-binary @- \
  -H "content-type: application/cose"
{
    "ballotCount": 0,
    "proposalId": "77374e16de0b2d61f58aec84d01e6218205d19c9401d2df127d893ce62576b81",
    "proposerId": "2af6cb6c0af07818186f7ef7151061174c3cb74b4a4c30a04a434f0c2b00a8c0",
    "proposalState": "Open"
}

Other members are then able to vote for the proposal using the returned proposal id.

Once the proposal has received enough votes under the rules of the Constitution (ie. ballots which evaluate to true), the network is opened to users. It is only then that users are able to execute transactions on the business logic defined by the enclave file (enclave.file configuration entry).