Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

specify replica-signed queries #163

Merged
merged 50 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
f6b55e3
specify replica-signed queries
mraszyk May 30, 2023
06a4713
drop note
mraszyk May 30, 2023
5924c82
fold content map in the value of request_id field
mraszyk May 31, 2023
41c45dc
update request CDDL
mraszyk May 31, 2023
4c63cd0
nodes -> subnet_nodes
mraszyk May 31, 2023
be66933
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jun 12, 2023
f889ffb
add condition on read_state paths
mraszyk Jun 12, 2023
c2066e2
make node signatures mandatory
mraszyk Jun 15, 2023
a345e0f
specify query call response verification
mraszyk Jun 15, 2023
067d004
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jun 16, 2023
76b0e17
refactor verify_response definition
mraszyk Jun 16, 2023
a16b1cd
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jun 21, 2023
bf990a1
update replica-signed query spec
mraszyk Jun 21, 2023
b40e802
update condition on return values
mraszyk Jun 21, 2023
cd860dc
update return_signature enum variants
mraszyk Jun 21, 2023
fe8c852
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jun 28, 2023
8be2868
rename optional request fields
mraszyk Jun 28, 2023
3e0b99b
Update spec/index.md
mraszyk Jun 29, 2023
d993538
model rejected query call because IC nodes computed different query r…
mraszyk Jun 29, 2023
b204aad
update cddl
mraszyk Jul 1, 2023
c267fe5
spelling
mraszyk Jul 1, 2023
8b5922d
fix variable names
mraszyk Jul 4, 2023
eaf7107
add note on multiple IC nodes evaluating query call
mraszyk Jul 4, 2023
e784e8d
typos
mraszyk Jul 4, 2023
2df3739
typo
mraszyk Jul 6, 2023
b596124
rename CanisterQuery to CanisterSignedQuery in formal spec
mraszyk Jul 8, 2023
11ff673
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jul 8, 2023
43d7fde
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jul 11, 2023
6091110
simplify
mraszyk Jul 11, 2023
640e74f
simplify
mraszyk Jul 11, 2023
cade854
simplify
mraszyk Jul 11, 2023
f5738c1
simplify
mraszyk Jul 11, 2023
2ae11ef
replace CanisterSignedQuery by CanisterQuery in formal text
mraszyk Jul 17, 2023
26b80cd
singleton -> containing one; a note on signatures being a list
mraszyk Jul 17, 2023
f550083
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jul 25, 2023
35425d3
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Jul 25, 2023
11f5997
add note on what recent enough means
mraszyk Jul 25, 2023
e625cf2
nodes -> node
mraszyk Aug 9, 2023
fbc8fe2
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Aug 16, 2023
3103163
make signatures in query responses non-optional
mraszyk Aug 19, 2023
6ac4e03
drop a note on optional signatures
mraszyk Aug 19, 2023
5e673b8
whitelist node paths in read_state requests
mraszyk Aug 22, 2023
ca2374f
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Aug 23, 2023
bf346e5
Update spec/index.md
mraszyk Sep 1, 2023
f8da3de
Update spec/index.md
mraszyk Sep 1, 2023
543745b
typo
mraszyk Sep 1, 2023
d77e24d
reformulation
mraszyk Sep 1, 2023
e79546f
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Sep 10, 2023
16d7ae6
Merge branch 'master' into mraszyk/replica-signed-queries
mraszyk Sep 13, 2023
f3971dc
update changelog
mraszyk Sep 13, 2023
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 spec/_attachments/interface-spec-changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Update algorithm computing the request and response hash in the HTTP Gateway including clarification of when the HTTP Gateway can allow for arbitrary certification version in the canister's response.
* Update conditions on requested paths in HTTP read state requests.
* Added new query methods in the Bitcoin API.
* Added node public keys to certified state and node signatures to query call responses.

### 0.20.0 (2023-07-11) {#0_20_0}
* IC Bitcoin API, ECDSA API, canister HTTPS outcalls API, and 128-bit cycles System API are considered stable.
Expand Down
12 changes: 10 additions & 2 deletions spec/_attachments/requests.cddl
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,19 @@ query-content = {
query-response = tagged<{
status: "replied"
reply: call-reply
signatures: [+ node-signature]
//
status: "rejected"
reject_code: unsigned
reject_message: text
? error_code: text
signatures: [+ node-signature]
}>

call-reply = {
arg : bytes
node-signature = {
timestamp: timestamp
signature: bytes
identity: principal
}

; user delegations
Expand All @@ -91,6 +95,10 @@ signed-delegation = {

; some common data types

call-reply = {
arg : bytes
}

principal = bytes .size (0..29)

pubkey = bytes
Expand Down
99 changes: 85 additions & 14 deletions spec/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,10 @@ Because this uses the lexicographic ordering of princpials, and the byte disting

:::

- `/subnet/<subnet_id>/node/<node_id>/public_key` (blob)

The public key of a node (a DER-encoded Ed25519 signing key, see [RFC 8410](https://tools.ietf.org/html/rfc8410) for reference) with principal `<node_id>` belonging to the subnet with principal `<subnet_id>`.

### Request status {#state-tree-request-status}

For each asynchronous request known to the Internet Computer, its status is in a subtree at `/request_status/<request_id>`. Please see [Overview of canister calling](#http-call-overview) for more details on how asynchronous requests work.
Expand Down Expand Up @@ -642,7 +646,11 @@ The HTTP response to this request consists of a CBOR (see [CBOR](#cbor)) map wit

If this `certificate` includes subnet delegations (possibly nested), then the `effective_canister_id` must be included in each delegation's canister id range (see [Delegation](#certification-delegation)).

The returned certificate reveals all values whose path is a suffix of a requested path. It also always reveals `/time`, even if not explicitly requested.
The returned certificate reveals all values whose path has a requested path as a prefix except for

- paths with prefix `/subnet/<subnet_id>/node` which are only contained in the returned certificate if `<effective_canister_id>` belongs to the canister ranges of the subnet `<subnet_id>`, i.e., if `<effective_canister_id>` belongs to the value at the path `/subnet/<subnet_id>/canister_ranges` in the state tree.

The returned certificate also always reveals `/time`, even if not explicitly requested.

:::note

Expand All @@ -659,7 +667,7 @@ All requested paths must have the following form:

- `/time`. Can always be requested.

- `/subnet`, `/subnet/<subnet_id>`, `/subnet/<subnet_id>/public_key`, `/subnet/<subnet_id>/canister_ranges`. Can always be requested.
- `/subnet`, `/subnet/<subnet_id>`, `/subnet/<subnet_id>/public_key`, `/subnet/<subnet_id>/canister_ranges`, `/subnet/<subnet_id>/node`, `/subnet/<subnet_id>/node/<node_id>`, `/subnet/<subnet_id>/node/<node_id>/public_key`. Can always be requested.

- `/request_status/<request_id>`, `/request_status/<request_id>/status`, `/request_status/<request_id>/reply`, `/request_status/<request_id>/reject_code`, `/request_status/<request_id>/reject_message`, `/request_status/<request_id>/error_code`. Can be requested if no path with such a prefix exists in the state tree or

Expand Down Expand Up @@ -709,9 +717,9 @@ Composite query methods are EXPERIMENTAL and there might be breaking changes of

:::

In order to make a query call to canister, the user makes a POST request to `/api/v2/canister/<effective_canister_id>/query`. The request body consists of an authentication envelope with a `content` map with the following fields:
In order to make a query call to a canister, the user makes a POST request to `/api/v2/canister/<effective_canister_id>/query`. The request body consists of an authentication envelope with a `content` map with the following fields:

- `request_type` (`text`): Always `query`.
- `request_type` (`text`): Always `"query"`.

- `sender`, `nonce`, `ingress_expiry`: See [Authentication](#authentication).

Expand All @@ -721,23 +729,74 @@ In order to make a query call to canister, the user makes a POST request to `/ap

- `arg` (`blob`): Argument to pass to the canister method.

If the call resulted in a reply, the response is a CBOR (see [CBOR](#cbor)) map with the following fields:
Canister methods that do not change the canister state (except for cycle balance changes due to message execution) can be executed more efficiently. This method provides that ability, and returns the canister's response directly within the HTTP response.

- `status` (`text`): `replied`
If the query call resulted in a reply, the response is a CBOR (see [CBOR](#cbor)) map with the following fields:

- `status` (`text`): `"replied"`

- `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data.

- `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response.

If the call resulted in a reject, the response is a CBOR map with the following fields:

- `status` (`text`): `rejected`
- `status` (`text`): `"rejected"`

- `reject_code` (`nat`): The reject code (see [Reject codes](#reject-codes)).

- `reject_message` (`text`): a textual diagnostic message.

- `error_code` (`text`): an optional implementation-specific textual error code (see [Error codes](#error-codes)).

Canister methods that do not change the canister state (except for cycle balance change due to message execution) can be executed more efficiently. This method provides that ability, and returns the canister's response directly within the HTTP response.
- `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response.

:::note

Although `signatures` only contains one node signature, we still declare its type to be a list to prevent future breaking changes
if we include more signatures in a future version of the protocol specification.

:::

The response to a query call contains a list with one signature for the returned response produced by the IC node that evaluated the query call. The signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields:

- `timestamp` (`nat`): the timestamp of the signature.

- `signature` (`blob`): the actual signature.

- `identity` (`principal`): the principal of the node producing the signature.

Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister/<effective_canister_id>/read_state`, the following predicate describes when the returned response `R` is correctly signed:

verify_response(Q, R, Cert)
= verify_cert(Cert) ∧
((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
ShuoWangNSL marked this conversation as resolved.
Show resolved Hide resolved
(SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧
mraszyk marked this conversation as resolved.
Show resolved Hide resolved
effective_canister_id ∈ Ranges ∧
∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures.
lookup(["subnet",SubnetId,"node",NodeId,"public_key"], Cert) = Found PK ∧
if R.status = "replied" then
verify_signature PK Sig ("\x0Bic-response" · hash_of_map({
status: "replied",
reply: R.reply,
timestamp: T,
request_id: hash_of_map(Q)}))
else
verify_signature PK Sig ("\x0Bic-response" · hash_of_map({
status: "rejected",
reject_code: R.reject_code,
reject_message: R.reject_message,
error_code: R.error_code,
timestamp: T,
request_id: hash_of_map(Q)}))

where `RootSubnetId` is the a priori known principal of the root subnet. Moreover, all timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation must be "recent enough".
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

:::note

This specification leaves it up to the client to define expiry times for the timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation. A reasonable expiry time for timestamps in `R.signatures` and the certificate `Cert` is 5 minutes (analogously to the maximum allowed ingress expiry enforced by the IC mainnet). Delegations require expiry times of at least a week since the IC mainnet refreshes the delegations only after replica upgrades which typically happen once a week.

:::

### Effective canister id {#http-effective-canister-id}

Expand Down Expand Up @@ -4918,14 +4977,23 @@ S.system_time <= Q.ingress_expiry

```

Query response
Query response `R`:

- if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reject (RejectCode, RejectMsg), _)` then

{status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: <implementation-specific>}
{status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: <implementation-specific>, signatures: Sigs}

- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reply Res, _)` then

- Else if `composite_query_helper(S, MAX_CYCLES_PER_QUERY, 0, Q.canister_id, Q.sender, Q.canister_id, Q.method_name, Q.arg) = (Reply R, _)` then
{status: "replied"; reply: {arg: Res}, signatures: Sigs}

{status: "replied"; reply: {arg: R}}
where the query `Q`, the response `R`, and a certificate `Cert'` that is obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister/<effective_canister_id>/read_state` satisfy the following:
ShuoWangNSL marked this conversation as resolved.
Show resolved Hide resolved

```html

verify_response(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough"
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

```

#### Certified state reads

Expand Down Expand Up @@ -4958,6 +5026,9 @@ The predicate `may_read_path` is defined as follows, implementing the access con
may_read_path(S, _, ["subnet", sid]) = True
may_read_path(S, _, ["subnet", sid, "public_key"]) = True
may_read_path(S, _, ["subnet", sid, "canister_ranges"]) = True
may_read_path(S, _, ["subnet", sid, "node"]) = True
may_read_path(S, _, ["subnet", sid, "node", nid]) = True
may_read_path(S, _, ["subnet", sid, "node", nid, "public_key"]) = True
may_read_path(S, _, ["request_status", Rid]) =
may_read_path(S, _, ["request_status", Rid, "status"]) =
may_read_path(S, _, ["request_status", Rid, "reply"]) =
Expand All @@ -4978,15 +5049,15 @@ The predicate `may_read_path` is defined as follows, implementing the access con

where `UTF8(name)` holds if `name` is encoded in UTF-8.

The response is a certificate `cert`, as specified in [Certification](#certification), which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in [The system state tree](#state-tree) that is a suffix of a path in `RS.paths` or of `["time"]`, we have
The response is a certificate `cert`, as specified in [Certification](#certification), which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in [The system state tree](#state-tree) that has a path in `RS.paths` or `["time"]` as a prefix, we have

lookup_in_tree(path, cert.tree) = lookup_in_tree(path, state_tree(S))

where `state_tree` constructs a labeled tree from the IC state `S` and the (so far underspecified) set of subnets `subnets`, as per [The system state tree](#state-tree)

state_tree(S) = {
"time": S.system_time;
"subnet": { subnet_id : { "public_key" : subnet_pk, "canister_ranges" : subnet_ranges } | (subnet_id, subnet_pk, subnet_ranges) ∈ subnets };
"subnet": { subnet_id : { "public_key" : subnet_pk, "canister_ranges" : subnet_ranges, "node": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ subnet_nodes } } | (subnet_id, subnet_pk, subnet_ranges, subnet_nodes) ∈ subnets };
"request_status": { request_id(R): request_status_tree(T) | (R ↦ (T, _)) ∈ S.requests };
"canister":
{ canister_id :
Expand Down
Loading