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 39 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
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
96 changes: 82 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`. 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.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

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

:::note

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 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.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

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

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

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

- (optional) `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

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.
- (optional) `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 optionally 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 decide how recent timestamps in `R.signatures`, the certificate `Cert`, and its optional delegation the client enforces. 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.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

:::

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

Expand Down Expand Up @@ -2604,7 +2663,7 @@ A reference implementation would likely maintain a separate list of `messages` f

#### API requests

We distinguish between the *asynchronous* API requests (type `Request`) passed to `/api/v2/…/call`, which may be present in the IC state, and the *synchronous* API requests passed to `/api/v2/…/read_state` and `/api/v2/…/query`, which are only ephemeral.
We distinguish between the *asynchronous* API requests (type `Request`) passed to `/api/v2/…/call`, which may be present in the IC state, and the *synchronous* API requests passed to `/api/v2/…/read_state`, and `/api/v2/…/query`, which are only ephemeral.
mraszyk marked this conversation as resolved.
Show resolved Hide resolved

These are the synchronous read messages:

Expand Down Expand Up @@ -4632,14 +4691,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

{status: "replied"; reply: {arg: Res}, 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 R, _)` then
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

{status: "replied"; reply: {arg: R}}
```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 @@ -4692,15 +4760,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