From f6b55e3c4b20706741345d1463b089b0a5d66fd0 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 30 May 2023 23:13:44 +0200 Subject: [PATCH 01/38] specify replica-signed queries --- spec/index.adoc | 61 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index cd672a81e..d8301ddcc 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -415,6 +415,9 @@ tagged = #6.55799(t) ; the CBOR tag + NOTE: Because this uses the lexicographic ordering of princpials, and the byte distinguishing the various classes of ids is at the _end_, this range by construction conceptually includes principals of various classes. This specification needs to take care that the fact that principals that are not canisters may appear in these ranges does not cause confusion. +* `/subnet//nodes//public_key` (blob) ++ +The public key of a node (a DER-encoded Ed25519 signing key, see https://tools.ietf.org/html/rfc8410[RFC 8410] for reference) with principal `` belonging to the subnet with principal ``. [#state-tree-request-status] === Request status @@ -629,17 +632,59 @@ In order to make a query call to canister, the user makes a POST request to `/ap * `method_name` (`text`): Name of the canister method to call * `arg` (`blob`): Argument to pass to the canister method +The HTTP response to a query call can contain a list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. +Every such signature (whose type is denoted as `node_signature_of_http_query_response`) is a CBOR (see <>) 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. + If the call resulted in a reply, the response is a CBOR (see <>) map with the following fields: -* `status` (`text`): `replied` -* `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. +* `status` (`text`): `"replied"` +* `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data ``. +* `node_signatures` (`[* node_signature_of_http_query_response]`): a list of node signatures for the returned query response + computed for the following value ++ +.... +hash_of_map({ + status: "replied", + reply: {arg: }, + timestamp: , + request_id: hash_of_map({request_type: "query", sender: , canister_id: , method_name: , arg: }) +}) +.... ++ +where `hash_of_map` is the <> and `` is the timestamp of the node when the signature was produced. 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_message` (`text`): a textual diagnostic message. -* `error_code` (text): an optional implementation-specific textual error code (see <>). +* `error_code` (`text`): an optional implementation-specific textual error code (see <>). +* `node_signatures` (`[* node_signature_of_http_query_response]`): a list of node signatures for the returned query response + computed for the following value ++ +.... +hash_of_map({ + status: "rejected", + reject_code: , + reject_message: , + error_code: , + timestamp: , + request_id: hash_of_map({request_type: "query", sender: , canister_id: , method_name: , arg: }) +}) +.... ++ +where `hash_of_map` is the <> and `` is the timestamp of the node when the signature was produced. + ++ +[NOTE] +==== +Note that, unlike for (update) calls, the `` of a query also depends on the query's sender. +For calls, the relationship between a call and its sender is established by only allowing the sender of the original request referenced by `` to query `/request_status/` in a read state request. +==== Canister methods that do not change the canister state can be executed more efficiently. This method provides that ability, and returns the canister’s response directly within the HTTP response. @@ -3910,17 +3955,17 @@ Read response:: * If `F(Q.Arg, Q.sender, Env) = Trap trap` then + .... -{status: rejected; reject_code: CANISTER_ERROR, reject_message: , error_code: } +{status: rejected; reject_code: CANISTER_ERROR, reject_message: , error_code: , node_signatures: } .... * Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reject (code, msg); …}` then + .... -{status: rejected; reject_code: : reject_message: , error_code: } +{status: rejected; reject_code: : reject_message: , error_code: , node_signatures: } .... * Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reply R; …}` then + .... -{status: success; reply: { arg : } } +{status: replied; reply: { arg : }, node_signatures: } .... ==== Certified state reads @@ -3967,7 +4012,7 @@ where `state_tree` constructs a labeled tree from the IC state `S` and the (so f .... 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, "nodes": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ nodes } } | (subnet_id, subnet_pk, subnet_ranges, nodes) ∈ subnets }; "request_status": { request_id(R): request_status_tree(T) | (R ↦ (T, _)) ∈ S.requests }; "canister": { canister_id : From 06a4713a55ffa89ca7d7dbe57b44b91b407f4adb Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 30 May 2023 23:23:00 +0200 Subject: [PATCH 02/38] drop note --- spec/index.adoc | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index d8301ddcc..b80c45d5d 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -651,7 +651,7 @@ hash_of_map({ status: "replied", reply: {arg: }, timestamp: , - request_id: hash_of_map({request_type: "query", sender: , canister_id: , method_name: , arg: }) + request_id: hash_of_map({request_type: "query", sender: , nonce: , ingress_expiry: , canister_id: , method_name: , arg: }) }) .... + @@ -673,19 +673,12 @@ hash_of_map({ reject_message: , error_code: , timestamp: , - request_id: hash_of_map({request_type: "query", sender: , canister_id: , method_name: , arg: }) + request_id: hash_of_map({request_type: "query", sender: , nonce: , ingress_expiry: , canister_id: , method_name: , arg: }) }) .... + where `hash_of_map` is the <> and `` is the timestamp of the node when the signature was produced. -+ -[NOTE] -==== -Note that, unlike for (update) calls, the `` of a query also depends on the query's sender. -For calls, the relationship between a call and its sender is established by only allowing the sender of the original request referenced by `` to query `/request_status/` in a read state request. -==== - Canister methods that do not change the canister state can be executed more efficiently. This method provides that ability, and returns the canister’s response directly within the HTTP response. [#http-effective-canister-id] From 5924c8276f9604a4c4574e70495bf91bb4669a28 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 31 May 2023 07:37:47 +0200 Subject: [PATCH 03/38] fold content map in the value of request_id field --- spec/index.adoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index b80c45d5d..54a50f21f 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -651,11 +651,11 @@ hash_of_map({ status: "replied", reply: {arg: }, timestamp: , - request_id: hash_of_map({request_type: "query", sender: , nonce: , ingress_expiry: , canister_id: , method_name: , arg: }) + request_id: hash_of_map(content) }) .... + -where `hash_of_map` is the <> and `` is the timestamp of the node when the signature was produced. +where `hash_of_map` is the <>, `content` is the CBOR map from the request, and `` is the timestamp of the node when the signature was produced. If the call resulted in a reject, the response is a CBOR map with the following fields: @@ -673,11 +673,11 @@ hash_of_map({ reject_message: , error_code: , timestamp: , - request_id: hash_of_map({request_type: "query", sender: , nonce: , ingress_expiry: , canister_id: , method_name: , arg: }) + request_id: hash_of_map(content) }) .... + -where `hash_of_map` is the <> and `` is the timestamp of the node when the signature was produced. +where `hash_of_map` is the <>, `content` is the CBOR map from the request, and `` is the timestamp of the node when the signature was produced. Canister methods that do not change the canister state can be executed more efficiently. This method provides that ability, and returns the canister’s response directly within the HTTP response. From 41c45dc82593186c7b1794a092c1de9062163c4a Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 31 May 2023 07:44:22 +0200 Subject: [PATCH 04/38] update request CDDL --- spec/index.adoc | 6 +++--- spec/requests.cddl | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 54a50f21f..0c89f61a4 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -633,7 +633,7 @@ 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 The HTTP response to a query call can contain a list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. -Every such signature (whose type is denoted as `node_signature_of_http_query_response`) is a CBOR (see <>) map with the following fields: +Every such signature (whose type is denoted as `node-signature-of-query-response`) is a CBOR (see <>) map with the following fields: * `timestamp` (`nat`): the timestamp of the signature. * `signature` (`blob`): the actual signature. @@ -643,7 +643,7 @@ If the call resulted in a reply, the response is a CBOR (see <>) map with * `status` (`text`): `"replied"` * `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data ``. -* `node_signatures` (`[* node_signature_of_http_query_response]`): a list of node signatures for the returned query response +* `node_signatures` (`[* node-signature-of-query-response]`): a list of node signatures for the returned query response computed for the following value + .... @@ -663,7 +663,7 @@ If the call resulted in a reject, the response is a CBOR map with the following * `reject_code` (`nat`): The reject code (see <>). * `reject_message` (`text`): a textual diagnostic message. * `error_code` (`text`): an optional implementation-specific textual error code (see <>). -* `node_signatures` (`[* node_signature_of_http_query_response]`): a list of node signatures for the returned query response +* `node_signatures` (`[* node-signature-of-query-response]`): a list of node signatures for the returned query response computed for the following value + .... diff --git a/spec/requests.cddl b/spec/requests.cddl index 74a509656..9c64c58a0 100644 --- a/spec/requests.cddl +++ b/spec/requests.cddl @@ -66,13 +66,21 @@ query-content = { query-response = tagged<{ status: "replied" reply: call-reply + node_signatures: [* node-signature-of-query-response] // status: "rejected" reject_code: unsigned reject_message: text ? error_code: text + node_signatures: [* node-signature-of-query-response] }> +node-signature-of-query-response = { + timestamp: timestamp + signature: bytes + identity: principal +} + call-reply = { arg : bytes } From 4c63cd009f391744d3a5edce7e0afa17e47c77d9 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 31 May 2023 07:46:42 +0200 Subject: [PATCH 05/38] nodes -> subnet_nodes --- spec/index.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.adoc b/spec/index.adoc index 0c89f61a4..d0ecd6a2e 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -4005,7 +4005,7 @@ where `state_tree` constructs a labeled tree from the IC state `S` and the (so f .... state_tree(S) = { "time": S.system_time; - "subnet": { subnet_id : { "public_key" : subnet_pk, "canister_ranges" : subnet_ranges, "nodes": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ nodes } } | (subnet_id, subnet_pk, subnet_ranges, nodes) ∈ subnets }; + "subnet": { subnet_id : { "public_key" : subnet_pk, "canister_ranges" : subnet_ranges, "nodes": { 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 : From f889ffbc4eeac9ec7c5dd1f9b82112ac6cf94433 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Mon, 12 Jun 2023 18:53:03 +0200 Subject: [PATCH 06/38] add condition on read_state paths --- spec/index.adoc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/index.adoc b/spec/index.adoc index 105480ebf..8bdb04648 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -593,7 +593,11 @@ The HTTP response to this request consists of a CBOR (see <>) map with the + If this `certificate` includes subnet delegations (possibly nested), then the `effective_canister_id` must be included in each delegation’s canister id range (see <>). -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 is a suffix of a requested path except for + + * paths with prefix `/subnet//nodes`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to `/subnet//canister_ranges`. + +The returned certificate also always reveals `/time`, even if not explicitly requested. All requested paths must have one of the following paths as prefix: From c2066e2dc1cf927fef123810b44fadc5685bef27 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Thu, 15 Jun 2023 21:24:54 +0200 Subject: [PATCH 07/38] make node signatures mandatory --- spec/index.adoc | 6 +++--- spec/requests.cddl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 8bdb04648..657ee1a88 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -636,7 +636,7 @@ In order to make a query call to canister, the user makes a POST request to `/ap * `method_name` (`text`): Name of the canister method to call * `arg` (`blob`): Argument to pass to the canister method -The HTTP response to a query call can contain a list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. +The HTTP response to a query call contains a list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature-of-query-response`) is a CBOR (see <>) map with the following fields: * `timestamp` (`nat`): the timestamp of the signature. @@ -647,7 +647,7 @@ If the call resulted in a reply, the response is a CBOR (see <>) map with * `status` (`text`): `"replied"` * `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data ``. -* `node_signatures` (`[* node-signature-of-query-response]`): a list of node signatures for the returned query response +* `node_signatures` (`[+ node-signature-of-query-response]`): a list of node signatures for the returned query response computed for the following value + .... @@ -667,7 +667,7 @@ If the call resulted in a reject, the response is a CBOR map with the following * `reject_code` (`nat`): The reject code (see <>). * `reject_message` (`text`): a textual diagnostic message. * `error_code` (`text`): an optional implementation-specific textual error code (see <>). -* `node_signatures` (`[* node-signature-of-query-response]`): a list of node signatures for the returned query response +* `node_signatures` (`[+ node-signature-of-query-response]`): a list of node signatures for the returned query response computed for the following value + .... diff --git a/spec/requests.cddl b/spec/requests.cddl index 9c64c58a0..bbf70be82 100644 --- a/spec/requests.cddl +++ b/spec/requests.cddl @@ -66,13 +66,13 @@ query-content = { query-response = tagged<{ status: "replied" reply: call-reply - node_signatures: [* node-signature-of-query-response] + node_signatures: [+ node-signature-of-query-response] // status: "rejected" reject_code: unsigned reject_message: text ? error_code: text - node_signatures: [* node-signature-of-query-response] + node_signatures: [+ node-signature-of-query-response] }> node-signature-of-query-response = { From a345e0f520132ffb6a9901ed8274e37e96943611 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Thu, 15 Jun 2023 22:35:38 +0200 Subject: [PATCH 08/38] specify query call response verification --- spec/index.adoc | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 657ee1a88..ea0b90521 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -4199,18 +4199,52 @@ Read response:: * If `F(Q.Arg, Q.sender, Env) = Trap trap` then + .... -{status: rejected; reject_code: CANISTER_ERROR, reject_message: , error_code: , node_signatures: } +{status: "rejected"; reject_code: CANISTER_ERROR, reject_message: , error_code: , node_signatures: } .... * Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reject (code, msg); …}` then + .... -{status: rejected; reject_code: : reject_message: , error_code: , node_signatures: } +{status: "rejected"; reject_code: : reject_message: , error_code: , node_signatures: } .... * Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reply R; …}` then + .... -{status: replied; reply: { arg : }, node_signatures: } -.... +{status: "replied"; reply: {arg: }, node_signatures: } +.... + +Given a certificate obtained by requesting the path `/subnet` via a read state request +to `/api/v2/canister//read_state`, the following predicate describes +when the returned response is correctly signed by the nodes: +.... +verify_response({status: "replied"; reply: {arg: }, node_signatures: Sigs}, Cert) + = verify_cert(Cert) ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ + (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ + ECID ∈ Ranges ∧ + ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ Sigs. + lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ + verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + status: "replied", + reply: {arg: }, + timestamp: T, + request_id: hash_of_map(E.content)})) +verify_response({status: "rejected"; reject_code: RejectCode, reject_message: RejectMessage, error_code: ErrorCode, node_signatures: Sigs}, Cert) + = verify_cert(Cert) ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ + (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ + ECID ∈ Ranges ∧ + ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ Sigs. + lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ + verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + status: "rejected", + reject_code: RejectCode, + reject_message: RejectMessage, + error_code: ErrorCode, + timestamp: T, + request_id: hash_of_map(E.content)})) +.... +where `RootSubnetId` is the a priori known subnet ID of the root subnet. Moreover, all timestamps in node signatures, +the certificate returned by the read state request, and its optional delegation must be "recent enough". ==== Certified state reads From 76b0e17cf826133a72df09a5dd45001ce3b1bdf1 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Fri, 16 Jun 2023 09:34:55 +0200 Subject: [PATCH 09/38] refactor verify_response definition --- spec/index.adoc | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 3a93416d3..b2f8244b7 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -4210,32 +4210,27 @@ Given a certificate obtained by requesting the path `/subnet` via a read state r to `/api/v2/canister//read_state`, the following predicate describes when the returned response is correctly signed by the nodes: .... -verify_response({status: "replied"; reply: {arg: }, node_signatures: Sigs}, Cert) +verify_response(R, Cert) = verify_cert(Cert) ∧ ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ ECID ∈ Ranges ∧ - ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ Sigs. + ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.Sigs. lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ - verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ - status: "replied", - reply: {arg: }, - timestamp: T, - request_id: hash_of_map(E.content)})) -verify_response({status: "rejected"; reject_code: RejectCode, reject_message: RejectMessage, error_code: ErrorCode, node_signatures: Sigs}, Cert) - = verify_cert(Cert) ∧ - ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ - (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ - ECID ∈ Ranges ∧ - ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ Sigs. - lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ - verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ - status: "rejected", - reject_code: RejectCode, - reject_message: RejectMessage, - error_code: ErrorCode, - timestamp: T, - request_id: hash_of_map(E.content)})) + if R.status = "replied" then + verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + status: "replied", + reply: R.reply, + timestamp: T, + request_id: hash_of_map(E.content)})) + else + verify_sig 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(E.content)})) .... where `RootSubnetId` is the a priori known subnet ID of the root subnet. Moreover, all timestamps in node signatures, the certificate returned by the read state request, and its optional delegation must be "recent enough". From bf990a10623f189d5314c66cb19c21ec0b2e6405 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 21 Jun 2023 09:07:37 +0200 Subject: [PATCH 10/38] update replica-signed query spec --- spec/index.adoc | 179 +++++++++++++++++++++++++-------------------- spec/requests.cddl | 45 ++++++++++-- 2 files changed, 135 insertions(+), 89 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index f02c04423..fa47b51aa 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -484,11 +484,12 @@ The concrete mechanism that users use to send requests to the Internet Computer * At `/api/v2/canister//call` the user can submit (asynchronous, potentially state-changing) calls. * At `/api/v2/canister//read_state` the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. * At `/api/v2/canister//query` the user can perform (synchronous, non-state-changing) query calls. +* At `/api/v2/canister//signed_query` the user can perform (synchronous, non-state-changing) query calls amended with signatures. * At `/api/v2/status` the user can retrieve status information about the Internet Computer. In these paths, the `` is the <> of the <>. -Requests to `/api/v2/canister//call`, `/api/v2/canister//read_state` and `/api/v2/canister//query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per <>) and request-specific content as described below. +Requests to `/api/v2/canister//call`, `/api/v2/canister//read_state`, `/api/v2/canister//query`, and `/api/v2/canister//signed_query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per <>) and request-specific content as described below. NOTE: This document does not yet explain how to find the location and port of the Internet Computer. @@ -593,9 +594,9 @@ The HTTP response to this request consists of a CBOR (see <>) map with the + If this `certificate` includes subnet delegations (possibly nested), then the `effective_canister_id` must be included in each delegation’s canister id range (see <>). -The returned certificate reveals all values whose path is a suffix of a requested path except for +The returned certificate reveals all values whose path has a requested path as a prefix except for - * paths with prefix `/subnet//nodes`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to `/subnet//canister_ranges`. + * paths with prefix `/subnet//nodes`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at the path `/subnet//canister_ranges` in the state tree. The returned certificate also always reveals `/time`, even if not explicitly requested. @@ -626,40 +627,23 @@ See <> for details on the state tree. [#http-query] === Request: Query call -A query call is a fast, but less secure way to call a canister. Only methods that are explicitly marked as “query methods” by the canister can be called this way. +Canister methods that do not change the canister state can be executed more efficiently. +A query call provides that ability: it is a fast, but less secure way to call a canister. +Only methods that are explicitly marked as “query methods” by the canister can be called this way. -In order to make a query call to canister, the user makes a POST request to `/api/v2/canister//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//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 <> * `canister_id` (`blob`): The principal of the canister to call. -* `method_name` (`text`): Name of the canister method to call -* `arg` (`blob`): Argument to pass to the canister method - -The HTTP response to a query call contains a list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. -Every such signature (whose type is denoted as `node-signature-of-query-response`) is a CBOR (see <>) 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. +* `method_name` (`text`): Name of the canister method to call. +* `arg` (`blob`): Argument to pass to the canister method. -If the call resulted in a reply, the response is a CBOR (see <>) map with the following fields: +If the query call resulted in a reply, the response is a CBOR (see <>) map with the following fields: * `status` (`text`): `"replied"` -* `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data ``. -* `node_signatures` (`[+ node-signature-of-query-response]`): a list of node signatures for the returned query response - computed for the following value -+ -.... -hash_of_map({ - status: "replied", - reply: {arg: }, - timestamp: , - request_id: hash_of_map(content) -}) -.... -+ -where `hash_of_map` is the <>, `content` is the CBOR map from the request, and `` is the timestamp of the node when the signature was produced. +* `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. If the call resulted in a reject, the response is a CBOR map with the following fields: @@ -667,23 +651,73 @@ If the call resulted in a reject, the response is a CBOR map with the following * `reject_code` (`nat`): The reject code (see <>). * `reject_message` (`text`): a textual diagnostic message. * `error_code` (`text`): an optional implementation-specific textual error code (see <>). -* `node_signatures` (`[+ node-signature-of-query-response]`): a list of node signatures for the returned query response - computed for the following value -+ + +[#http-signed-query] +=== Request: Signed query call + +NOTE: The signed query call API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. + +A *signed* query call is a query call amended with signatures produced by IC nodes evaluating the query call to certify the query response. + +In order to make a signed query call to a canister, the user makes a POST request to `/api/v2/canister//signed_query`. +The request body has the same structure as the request body for <> with the `content` map having the following *additional* fields: + +* (optional) `return_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"no"`, `"one"`. +* (optional) `return_certificate` (`text`): Determines whether a certificate (as specified in <>) is returned in the response; possible values are `"no"`, `"yes"`. + +The response is a CBOR (see <>) with the same fields as for <> response and the following *additional* fields: + +* (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. +* (optional) `certificate` (`blob`): a certificate, as specified in <>. + +Unless `return_signatures` is set to `"no"` in the request, the response to a signed query call contains a non-empty list of signatures +for the returned response produced by the individual IC nodes that computed the same returned response. +Every such signature (whose type is denoted as `node-signature`) is a CBOR (see <>) 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. + +Unless `return_certificate` is set to `"no"` in the request, the response to a signed query call contains a certificate containing +all paths in the state tree with prefixes + +* `/time`. Always contained in the certificate. +* `/subnet//canister_ranges`. Contained in the certificate if the certificate does not have a subnet delegation, as specified in <>. +* `/subnet//nodes//public_key`. Contained in the certificate if `` is the principal of a node in `signatures`. + +where `` characterizes the subnet hosting the requested canister. + +Given a response `R` and a certificate `Cert` that is + +* contained in the response, i.e., `Cert = R.certificate`, or +* obtained by requesting the path `/subnet` in a *separate* read state request to `/api/v2/canister//read_state`, + +the following predicate describes when the returned response `R` is correctly signed by the nodes: .... -hash_of_map({ - status: "rejected", - reject_code: , - reject_message: , - error_code: , - timestamp: , - request_id: hash_of_map(content) -}) +verify_response(R, Cert) + = verify_cert(Cert) ∧ + ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ + (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ + ECID ∈ Ranges ∧ + ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. + lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ + if R.status = "replied" then + verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + status: "replied", + reply: R.reply, + timestamp: T, + request_id: hash_of_map(E.content)})) + else + verify_sig 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(E.content)})) .... -+ -where `hash_of_map` is the <>, `content` is the CBOR map from the request, and `` is the timestamp of the node when the signature was produced. - -Canister methods that do not change the canister state can be executed more efficiently. This method provides that ability, and returns the canister’s response directly within the HTTP response. +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". [#http-effective-canister-id] === Effective canister id @@ -2489,7 +2523,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`, `/api/v2/…/query`, and `/api/v2/…/signed_query`, which are only ephemeral. These are the synchronous read messages: .... @@ -2508,6 +2542,8 @@ APIReadRequest canister_id : CanisterId; method_name : Text; arg : Blob; + return_certificate : Text; + return_signatures : Text; } .... @@ -2719,7 +2755,7 @@ Based on this abstract notion of the state, we can describe the behavior of the * Asynchronous API requests that are submitted via `/api/v2/…/call`. These transitions describe checks that the request must pass to be considered received. * Spontaneous transitions that model the internal behavior of the IC, by describing conditions on the state that allow the transition to happen, and the state after. - * Responses to reads (i.e. `/api/v2/…/read_state` and `/api/v2/…/query`). By definition, these do _not_ change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. + * Responses to reads (i.e. `/api/v2/…/read_state`, `/api/v2/…/query`, and `/api/v2/…/signed_query`). By definition, these do _not_ change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. The state transitions are not complete with regard to error handling. For example, the behavior of sending a request to a non-existent canister is not specified here. For now, we trust implementors to make sensible decisions there. @@ -4160,7 +4196,8 @@ S with ==== Query call -Canister query calls to `/api/v2/canister//query` can be executed directly. They can only be executed against canisters which have a status of `Running` and are also not frozen. +Canister query calls to `/api/v2/canister//query` and `/api/v2/canister//signed_query` can be executed directly. +They can only be executed against canisters which have a status of `Running` and are also not frozen. During the execution of a query call, a certificate is provided to the canister that is valid, contains a current state tree (or “recent enough”; the specification is currently vague about how old the certificate may be) and reveals the canister’s <>. @@ -4189,51 +4226,31 @@ Conditions:: canister_version = S.canister_version[Q.receiver]; } .... -Read response:: +Query response `R`: * If `F(Q.Arg, Q.sender, Env) = Trap trap` then + .... -{status: "rejected"; reject_code: CANISTER_ERROR, reject_message: , error_code: , node_signatures: } +{status: "rejected"; reject_code: CANISTER_ERROR, reject_message: , error_code: , signatures: Sigs, certificate: Cert'} .... * Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reject (code, msg); …}` then + .... -{status: "rejected"; reject_code: : reject_message: , error_code: , node_signatures: } +{status: "rejected"; reject_code: : reject_message: , error_code: , signatures: Sigs, certificate: Cert'} .... -* Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reply R; …}` then +* Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reply Res; …}` then + .... -{status: "replied"; reply: {arg: }, node_signatures: } +{status: "replied"; reply: {arg: }, signatures: Sigs, certificate: Cert'} .... - -Given a certificate obtained by requesting the path `/subnet` via a read state request -to `/api/v2/canister//read_state`, the following predicate describes -when the returned response is correctly signed by the nodes: ++ +where `Sigs` and `Cert'` satisfies the following: .... -verify_response(R, Cert) - = verify_cert(Cert) ∧ - ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ - (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ - ECID ∈ Ranges ∧ - ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.Sigs. - lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ - if R.status = "replied" then - verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ - status: "replied", - reply: R.reply, - timestamp: T, - request_id: hash_of_map(E.content)})) - else - verify_sig 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(E.content)})) +Q.return_signatures = null => Sigs = null +Q.return_signatures = "no" => Sigs = [] +Q.return_signatures = "one" => |Sigs| = 1 +Q.return_certificate = null ∨ Q.return_certificate = "no" => Cert' = null +Q.return_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or “recent enough” .... -where `RootSubnetId` is the a priori known subnet ID of the root subnet. Moreover, all timestamps in node signatures, -the certificate returned by the read state request, and its optional delegation must be "recent enough". ==== Certified state reads @@ -4271,7 +4288,7 @@ may_read_path(S, _, _) = False .... where `UTF8(name)` holds if `name` is encoded in UTF-8. -The response is a certificate `cert`, as specified in <>, which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in <> that is a suffix of a path in `RS.paths` or of `["time"]`, we have +The response is a certificate `cert`, as specified in <>, which passes `verify_cert` (assuming `S.root_key` as the root of trust), and where for every `path` documented in <> that has a path in `RS.paths` or `["time"]` as a prefix, we have .... lookup(path, cert) = lookup_in_tree(path, state_tree(S)) .... diff --git a/spec/requests.cddl b/spec/requests.cddl index bbf70be82..d3422e11e 100644 --- a/spec/requests.cddl +++ b/spec/requests.cddl @@ -5,8 +5,10 @@ start = call-request / read-state-request / query-request / + signed-query-request / read-state-response / - query-response + query-response / + signed-query-response ; common wrappers @@ -66,25 +68,48 @@ query-content = { query-response = tagged<{ status: "replied" reply: call-reply - node_signatures: [+ node-signature-of-query-response] // status: "rejected" reject_code: unsigned reject_message: text ? error_code: text - node_signatures: [+ node-signature-of-query-response] }> -node-signature-of-query-response = { +; A request as submitted to /api/v2/.../signed_query +signed-query-request = envelope +signed-query-content = { + request_type: "query" + ? nonce : bytes + ingress_expiry : timestamp + sender : principal + canister_id : principal + method_name : text + arg : bytes + ? return_signatures : text + ? return_certificate : text +} + +; The response, as returned from /api/v2/.../signed_query +signed-query-response = tagged<{ + status: "replied" + reply: call-reply + ? signatures: [+ node-signature] + ? certificate : bytes + // + status: "rejected" + reject_code: unsigned + reject_message: text + ? error_code: text + ? signatures: [+ node-signature] + ? certificate : bytes +}> + +node-signature = { timestamp: timestamp signature: bytes identity: principal } -call-reply = { - arg : bytes -} - ; user delegations signed-delegation = { @@ -99,6 +124,10 @@ signed-delegation = { ; some common data types +call-reply = { + arg : bytes +} + principal = bytes .size (0..29) pubkey = bytes From b40e8023d595bbbd96ec483318067e8fa0169fd9 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 21 Jun 2023 14:30:58 +0200 Subject: [PATCH 11/38] update condition on return values --- spec/index.adoc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index fa47b51aa..6a22c764c 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -670,7 +670,7 @@ The response is a CBOR (see <>) with the same fields as for <> * (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. * (optional) `certificate` (`blob`): a certificate, as specified in <>. -Unless `return_signatures` is set to `"no"` in the request, the response to a signed query call contains a non-empty list of signatures +Unless `return_signatures` is null or set to `"no"` in the request, the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see <>) map with the following fields: @@ -678,7 +678,7 @@ Every such signature (whose type is denoted as `node-signature`) is a CBOR (see * `signature` (`blob`): the actual signature. * `identity` (`principal`): the principal of the node producing the signature. -Unless `return_certificate` is set to `"no"` in the request, the response to a signed query call contains a certificate containing +Unless `return_certificate` is null or set to `"no"` in the request, the response to a signed query call contains a certificate containing all paths in the state tree with prefixes * `/time`. Always contained in the certificate. @@ -4245,8 +4245,7 @@ Query response `R`: + where `Sigs` and `Cert'` satisfies the following: .... -Q.return_signatures = null => Sigs = null -Q.return_signatures = "no" => Sigs = [] +Q.return_signatures = null ∨ Q.return_signatures = "no" => Sigs = null Q.return_signatures = "one" => |Sigs| = 1 Q.return_certificate = null ∨ Q.return_certificate = "no" => Cert' = null Q.return_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or “recent enough” From cd860dcdf8572efbfc58531d289d8a41ccf6c1dc Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 21 Jun 2023 14:31:54 +0200 Subject: [PATCH 12/38] update return_signature enum variants --- spec/index.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/index.adoc b/spec/index.adoc index 6a22c764c..7deb39469 100644 --- a/spec/index.adoc +++ b/spec/index.adoc @@ -662,7 +662,7 @@ A *signed* query call is a query call amended with signatures produced by IC nod In order to make a signed query call to a canister, the user makes a POST request to `/api/v2/canister//signed_query`. The request body has the same structure as the request body for <> with the `content` map having the following *additional* fields: -* (optional) `return_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"no"`, `"one"`. +* (optional) `return_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"none"`, `"one"`. * (optional) `return_certificate` (`text`): Determines whether a certificate (as specified in <>) is returned in the response; possible values are `"no"`, `"yes"`. The response is a CBOR (see <>) with the same fields as for <> response and the following *additional* fields: @@ -670,7 +670,7 @@ The response is a CBOR (see <>) with the same fields as for <> * (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. * (optional) `certificate` (`blob`): a certificate, as specified in <>. -Unless `return_signatures` is null or set to `"no"` in the request, the response to a signed query call contains a non-empty list of signatures +Unless `return_signatures` is null or set to `"none"` in the request, the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see <>) map with the following fields: @@ -4245,7 +4245,7 @@ Query response `R`: + where `Sigs` and `Cert'` satisfies the following: .... -Q.return_signatures = null ∨ Q.return_signatures = "no" => Sigs = null +Q.return_signatures = null ∨ Q.return_signatures = "none" => Sigs = null Q.return_signatures = "one" => |Sigs| = 1 Q.return_certificate = null ∨ Q.return_certificate = "no" => Cert' = null Q.return_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or “recent enough” From 8be28681914d6b418dfd2c8215d40f79cf40aad8 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 28 Jun 2023 17:41:50 +0200 Subject: [PATCH 13/38] rename optional request fields --- spec/index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/index.md b/spec/index.md index f120979ae..f0667692a 100644 --- a/spec/index.md +++ b/spec/index.md @@ -722,9 +722,9 @@ A **signed** query call is a query call amended with signatures produced by IC n In order to make a signed query call to a canister, the user makes a POST request to `/api/v2/canister//signed_query`. The request body has the same structure as the request body for [Request: Query call](#http-query) with the `content` map having the following **additional** fields: -- (optional) `return_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"none"`, `"one"`. +- (optional) `requested_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"none"`, `"one"`. -- (optional) `return_certificate` (`text`): Determines whether a certificate (as specified in [Certification](#certification)) is returned in the response; possible values are `"no"`, `"yes"`. +- (optional) `requested_certificate` (`text`): Determines whether a certificate (as specified in [Certification](#certification)) is returned in the response; possible values are `"no"`, `"yes"`. The response is a CBOR (see [CBOR](#cbor)) with the same fields as for [Request: Query call](#http-query) response and the following **additional** fields: @@ -732,7 +732,7 @@ The response is a CBOR (see [CBOR](#cbor)) with the same fields as for [Request: - (optional) `certificate` (`blob`): a certificate, as specified in [Certification](#certification). -Unless `return_signatures` is null or set to `"none"` in the request, the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields: +Unless `requested_signatures` is null or set to `"none"` in the request, the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such 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. @@ -740,7 +740,7 @@ Unless `return_signatures` is null or set to `"none"` in the request, the respon - `identity` (`principal`): the principal of the node producing the signature. -Unless `return_certificate` is null or set to `"no"` in the request, the response to a signed query call contains a certificate containing all paths in the state tree with prefixes +Unless `requested_certificate` is null or set to `"no"` in the request, the response to a signed query call contains a certificate containing all paths in the state tree with prefixes - `/time`. Always contained in the certificate. @@ -2669,8 +2669,8 @@ These are the synchronous read messages: canister_id : CanisterId; method_name : Text; arg : Blob; - return_certificate : Text; - return_signatures : Text; + requested_certificate : Text; + requested_signatures : Text; } Signed delegations contain the (unsigned) delegation data in a nested record, next to the signature of that data. @@ -4609,10 +4609,10 @@ Query response `R`: \* If `F(Q.Arg, Q.sender, Env) = Trap trap` then ```html -Q.return_signatures = null ∨ Q.return_signatures = "none" => Sigs = null -Q.return_signatures = "one" => |Sigs| = 1 -Q.return_certificate = null ∨ Q.return_certificate = "no" => Cert' = null -Q.return_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" +Q.requested_signatures = null ∨ Q.requested_signatures = "none" => Sigs = null +Q.requested_signatures = "one" => |Sigs| = 1 +Q.requested_certificate = null ∨ Q.requested_certificate = "no" => Cert' = null +Q.requested_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" ``` From 3e0b99b50f0e1c77351942beb185c7925ca93acc Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Thu, 29 Jun 2023 15:17:59 +0200 Subject: [PATCH 14/38] Update spec/index.md Co-authored-by: oggy-dfin <89794951+oggy-dfin@users.noreply.github.com> --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index f0667692a..6208cd5d4 100644 --- a/spec/index.md +++ b/spec/index.md @@ -750,7 +750,7 @@ Unless `requested_certificate` is null or set to `"no"` in the request, the resp where `` characterizes the subnet hosting the requested canister. -Given a response `R` and a certificate `Cert` that is +Given a response `R` and a certificate `Cert` that is either - contained in the response, i.e., `Cert = R.certificate`, or From d993538d7d896ffe74a300a675baba44f3c9c812 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Thu, 29 Jun 2023 15:50:02 +0200 Subject: [PATCH 15/38] model rejected query call because IC nodes computed different query responses --- spec/index.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/spec/index.md b/spec/index.md index 6208cd5d4..34cff321c 100644 --- a/spec/index.md +++ b/spec/index.md @@ -732,7 +732,11 @@ The response is a CBOR (see [CBOR](#cbor)) with the same fields as for [Request: - (optional) `certificate` (`blob`): a certificate, as specified in [Certification](#certification). -Unless `requested_signatures` is null or set to `"none"` in the request, the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields: +If the IC nodes evaluating the query call do not compute the same query response (e.g., because they evaluated the query call on different states), +then the query call is rejected with the reject code `SYS_TRANSIENT` (2). If the query call is rejected with the reject code `SYS_TRANSIENT` (2), +then no signature and certificate is contained in the response. + +Unless `requested_signatures` is null or set to `"none"` in the request and unless the query call is rejected with the reject code `SYS_TRANSIENT` (2), the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such 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. @@ -740,7 +744,7 @@ Unless `requested_signatures` is null or set to `"none"` in the request, the res - `identity` (`principal`): the principal of the node producing the signature. -Unless `requested_certificate` is null or set to `"no"` in the request, the response to a signed query call contains a certificate containing all paths in the state tree with prefixes +Unless `requested_certificate` is null or set to `"no"` in the request and unless the query call is rejected with the reject code `SYS_TRANSIENT` (2), the response to a signed query call contains a certificate containing all paths in the state tree with prefixes - `/time`. Always contained in the certificate. @@ -758,8 +762,12 @@ Given a response `R` and a certificate `Cert` that is either the following predicate describes when the returned response `R` is correctly signed by the nodes: - verify_response(R, Cert) - = verify_cert(Cert) ∧ + verify_response(Q, R, Cert) + = (Q.requested_signatures = null ∨ Q.requested_signatures = "none" ∨ R.reject_code = SYS_TRANSIENT => Sigs = null) ∧ + (Q.requested_signatures = "one" ∧ R.reject_code ≠ SYS_TRANSIENT => |Sigs| = 1) ∧ + (Q.requested_certificate = null ∨ Q.requested_certificate = "no" ∨ R.reject_code = SYS_TRANSIENT => Cert = null) ∧ + (Q.requested_certificate = "yes" ∧ R.reject_code ≠ SYS_TRANSIENT => verify_response(R, Cert)) ∧ + verify_cert(Cert) ∧ ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ ECID ∈ Ranges ∧ @@ -4591,11 +4599,11 @@ Env = { ``` -Query response `R`: \* If `F(Q.Arg, Q.sender, Env) = Trap trap` then +Query response `R`: -\+ +- If `F(Q.Arg, Q.sender, Env) = Trap trap` then - {status: "rejected"; reject_code: CANISTER_ERROR, reject_message: , error_code: , signatures: Sigs, certificate: Cert'} + {status: "rejected"; reject_code: CANISTER_ERROR, reject_message: , error_code: , signatures: Sigs, certificate: Cert'} - Else if `F(Q.Arg, Q.sender, Env) = Return {response = Reject (code, msg); …}` then @@ -4609,10 +4617,7 @@ Query response `R`: \* If `F(Q.Arg, Q.sender, Env) = Trap trap` then ```html -Q.requested_signatures = null ∨ Q.requested_signatures = "none" => Sigs = null -Q.requested_signatures = "one" => |Sigs| = 1 -Q.requested_certificate = null ∨ Q.requested_certificate = "no" => Cert' = null -Q.requested_certificate = "yes" => verify_response(R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" +verify_responses(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" ``` From b204aadfe7d8fd4ecdbced243a9ef46bcdf48ef5 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Sat, 1 Jul 2023 14:26:56 +0200 Subject: [PATCH 16/38] update cddl --- spec/_attachments/requests.cddl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/_attachments/requests.cddl b/spec/_attachments/requests.cddl index d3422e11e..84aad8b0e 100644 --- a/spec/_attachments/requests.cddl +++ b/spec/_attachments/requests.cddl @@ -85,8 +85,8 @@ signed-query-content = { canister_id : principal method_name : text arg : bytes - ? return_signatures : text - ? return_certificate : text + ? requested_signatures : text + ? requested_certificate : text } ; The response, as returned from /api/v2/.../signed_query From c267fe55bb5a4ae3fdf527d51445bd7d16788ef6 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Sat, 1 Jul 2023 14:39:15 +0200 Subject: [PATCH 17/38] spelling --- spec/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/index.md b/spec/index.md index 34cff321c..dd957dbd4 100644 --- a/spec/index.md +++ b/spec/index.md @@ -752,7 +752,7 @@ Unless `requested_certificate` is null or set to `"no"` in the request and unles - `/subnet//nodes//public_key`. Contained in the certificate if `` is the principal of a node in `signatures`. -where `` characterizes the subnet hosting the requested canister. +where `` characterizes the subnet to which the requested canister belongs. Given a response `R` and a certificate `Cert` that is either @@ -766,7 +766,6 @@ the following predicate describes when the returned response `R` is correctly si = (Q.requested_signatures = null ∨ Q.requested_signatures = "none" ∨ R.reject_code = SYS_TRANSIENT => Sigs = null) ∧ (Q.requested_signatures = "one" ∧ R.reject_code ≠ SYS_TRANSIENT => |Sigs| = 1) ∧ (Q.requested_certificate = null ∨ Q.requested_certificate = "no" ∨ R.reject_code = SYS_TRANSIENT => Cert = null) ∧ - (Q.requested_certificate = "yes" ∧ R.reject_code ≠ SYS_TRANSIENT => verify_response(R, Cert)) ∧ verify_cert(Cert) ∧ ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ @@ -4613,7 +4612,7 @@ Query response `R`: {status: "replied"; reply: {arg: }, signatures: Sigs, certificate: Cert'} - where `Sigs` and `Cert'` satisfies the following: +where the query `Q` and the response `R` satisfy the following: ```html From 8b5922d4a1a8c44ae5dfd33464c19b5b997a6767 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 4 Jul 2023 09:29:33 +0200 Subject: [PATCH 18/38] fix variable names --- spec/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/index.md b/spec/index.md index dd957dbd4..1c70b5e42 100644 --- a/spec/index.md +++ b/spec/index.md @@ -754,22 +754,22 @@ Unless `requested_certificate` is null or set to `"no"` in the request and unles where `` characterizes the subnet to which the requested canister belongs. -Given a response `R` and a certificate `Cert` that is either +Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is either - contained in the response, i.e., `Cert = R.certificate`, or -- obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister//read_state`, +- obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister//read_state`, the following predicate describes when the returned response `R` is correctly signed by the nodes: verify_response(Q, R, Cert) - = (Q.requested_signatures = null ∨ Q.requested_signatures = "none" ∨ R.reject_code = SYS_TRANSIENT => Sigs = null) ∧ - (Q.requested_signatures = "one" ∧ R.reject_code ≠ SYS_TRANSIENT => |Sigs| = 1) ∧ + = (Q.requested_signatures = null ∨ Q.requested_signatures = "none" ∨ R.reject_code = SYS_TRANSIENT => R.signatures = null) ∧ + (Q.requested_signatures = "one" ∧ R.reject_code ≠ SYS_TRANSIENT => |R.signatures| = 1) ∧ (Q.requested_certificate = null ∨ Q.requested_certificate = "no" ∨ R.reject_code = SYS_TRANSIENT => Cert = null) ∧ verify_cert(Cert) ∧ ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ - ECID ∈ Ranges ∧ + effective_canister_id ∈ Ranges ∧ ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ if R.status = "replied" then @@ -777,7 +777,7 @@ the following predicate describes when the returned response `R` is correctly si status: "replied", reply: R.reply, timestamp: T, - request_id: hash_of_map(E.content)})) + request_id: hash_of_map(Q)})) else verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ status: "rejected", @@ -785,7 +785,7 @@ the following predicate describes when the returned response `R` is correctly si reject_message: R.reject_message, error_code: R.error_code, timestamp: T, - request_id: hash_of_map(E.content)})) + 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". From eaf710755fd2089e956417d8d3efdb0f1b1026da Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 4 Jul 2023 09:39:19 +0200 Subject: [PATCH 19/38] add note on multiple IC nodes evaluating query call --- spec/index.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index 1c70b5e42..e30b87c18 100644 --- a/spec/index.md +++ b/spec/index.md @@ -734,7 +734,15 @@ The response is a CBOR (see [CBOR](#cbor)) with the same fields as for [Request: If the IC nodes evaluating the query call do not compute the same query response (e.g., because they evaluated the query call on different states), then the query call is rejected with the reject code `SYS_TRANSIENT` (2). If the query call is rejected with the reject code `SYS_TRANSIENT` (2), -then no signature and certificate is contained in the response. +then no signatures and certificate are contained in the response. + +:::note + +Note that currently only one IC nodes evaluates a query call and thus the query call cannot be rejected with the reject code `SYS_TRANSIENT` (2) +because the IC nodes evaluating the query call do not compute the same query response. However, query calls might be evaluated +by multiple IC nodes in the futures and thus we specify the general behavior. + +::: Unless `requested_signatures` is null or set to `"none"` in the request and unless the query call is rejected with the reject code `SYS_TRANSIENT` (2), the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields: From e784e8d368d9b02818bd9d34ae138c198f9fd975 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 4 Jul 2023 09:42:58 +0200 Subject: [PATCH 20/38] typos --- spec/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.md b/spec/index.md index e30b87c18..d39cdd05e 100644 --- a/spec/index.md +++ b/spec/index.md @@ -738,9 +738,9 @@ then no signatures and certificate are contained in the response. :::note -Note that currently only one IC nodes evaluates a query call and thus the query call cannot be rejected with the reject code `SYS_TRANSIENT` (2) +Currently only one IC node evaluates a query call and thus the query call cannot be rejected with the reject code `SYS_TRANSIENT` (2) because the IC nodes evaluating the query call do not compute the same query response. However, query calls might be evaluated -by multiple IC nodes in the futures and thus we specify the general behavior. +by multiple IC nodes in the future and thus we specify the general behavior. ::: From 2df37390c4e47452808ef0f2016ba21fd5511e4e Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Thu, 6 Jul 2023 17:08:38 +0200 Subject: [PATCH 21/38] typo --- spec/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.md b/spec/index.md index d39cdd05e..7586066e7 100644 --- a/spec/index.md +++ b/spec/index.md @@ -781,13 +781,13 @@ the following predicate describes when the returned response `R` is correctly si ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ if R.status = "replied" then - verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ status: "replied", reply: R.reply, timestamp: T, request_id: hash_of_map(Q)})) else - verify_sig PK Sig ("\x0Bic-response" · hash_of_map({ + verify_signature PK Sig ("\x0Bic-response" · hash_of_map({ status: "rejected", reject_code: R.reject_code, reject_message: R.reject_message, From b596124081eb3ef5fe7f5f103b1173e32e04b88f Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Sat, 8 Jul 2023 16:58:41 +0200 Subject: [PATCH 22/38] rename CanisterQuery to CanisterSignedQuery in formal spec --- spec/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.md b/spec/index.md index 7586066e7..1dafdffc6 100644 --- a/spec/index.md +++ b/spec/index.md @@ -2677,7 +2677,7 @@ These are the synchronous read messages: sender : UserId; paths : List(Path); } - | CanisterQuery = { + | CanisterSignedQuery = { nonce : Blob; ingress_expiry : Nat; sender : UserId; @@ -4582,7 +4582,7 @@ Conditions ```html -E.content = CanisterQuery Q +E.content = CanisterSignedQuery Q Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) is_effective_canister_id(E.content, ECID) S.system_time <= Q.ingress_expiry From 6091110967a77262a3aef4cc62a8d2015f25a7e1 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 11 Jul 2023 18:02:30 +0200 Subject: [PATCH 23/38] simplify --- spec/_attachments/requests.cddl | 31 +------------ spec/index.md | 77 +++++---------------------------- 2 files changed, 13 insertions(+), 95 deletions(-) diff --git a/spec/_attachments/requests.cddl b/spec/_attachments/requests.cddl index 84aad8b0e..5fee31431 100644 --- a/spec/_attachments/requests.cddl +++ b/spec/_attachments/requests.cddl @@ -5,10 +5,8 @@ start = call-request / read-state-request / query-request / - signed-query-request / read-state-response / - query-response / - signed-query-response + query-response ; common wrappers @@ -66,42 +64,15 @@ query-content = { ; The response, as returned from /api/v2/.../query query-response = tagged<{ - status: "replied" - reply: call-reply - // - status: "rejected" - reject_code: unsigned - reject_message: text - ? error_code: text -}> - -; A request as submitted to /api/v2/.../signed_query -signed-query-request = envelope -signed-query-content = { - request_type: "query" - ? nonce : bytes - ingress_expiry : timestamp - sender : principal - canister_id : principal - method_name : text - arg : bytes - ? requested_signatures : text - ? requested_certificate : text -} - -; The response, as returned from /api/v2/.../signed_query -signed-query-response = tagged<{ status: "replied" reply: call-reply ? signatures: [+ node-signature] - ? certificate : bytes // status: "rejected" reject_code: unsigned reject_message: text ? error_code: text ? signatures: [+ node-signature] - ? certificate : bytes }> node-signature = { diff --git a/spec/index.md b/spec/index.md index 9ac7207af..8b294dd48 100644 --- a/spec/index.md +++ b/spec/index.md @@ -517,15 +517,13 @@ The concrete mechanism that users use to send requests to the Internet Computer - At `/api/v2/canister//read_state` the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. -- At `/api/v2/canister//query` the user can perform (synchronous, non-state-changing) query calls. - -- At `/api/v2/canister//signed_query` the user can perform (synchronous, non-state-changing) query calls amended with signatures. +- At `/api/v2/canister//query` the user can perform (synchronous, non-state-changing) query calls amended with signatures. - At `/api/v2/status` the user can retrieve status information about the Internet Computer. In these paths, the `` is the [textual representation](#textual-ids) of the [*effective* canister id](#http-effective-canister-id). -Requests to `/api/v2/canister//call`, `/api/v2/canister//read_state`, `/api/v2/canister//query`, and `/api/v2/canister//signed_query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per [Authentication](#authentication)) and request-specific content as described below. +Requests to `/api/v2/canister//call`, `/api/v2/canister//read_state`, and `/api/v2/canister//query` are POST requests with a CBOR-encoded request body, which consists of a authentication envelope (as per [Authentication](#authentication)) and request-specific content as described below. :::note @@ -719,12 +717,16 @@ In order to make a query call to a canister, the user makes a POST request to `/ - `arg` (`blob`): Argument to pass to the canister method. +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. + 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. +- (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures 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"` @@ -735,43 +737,9 @@ If the call resulted in a reject, the response is a CBOR map with the following - `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. - -### Request: Signed query call {#http-signed-query} - -:::note - -The signed query call API is considered EXPERIMENTAL. Canister developers must be aware that the API may evolve in a non-backward-compatible way. - -::: - -A **signed** query call is a query call amended with signatures produced by IC nodes evaluating the query call to certify the query response. - -In order to make a signed query call to a canister, the user makes a POST request to `/api/v2/canister//signed_query`. The request body has the same structure as the request body for [Request: Query call](#http-query) with the `content` map having the following **additional** fields: - -- (optional) `requested_signatures` (`text`): Determines whether and how many signatures are returned in the response; possible values are `"none"`, `"one"`. - -- (optional) `requested_certificate` (`text`): Determines whether a certificate (as specified in [Certification](#certification)) is returned in the response; possible values are `"no"`, `"yes"`. - -The response is a CBOR (see [CBOR](#cbor)) with the same fields as for [Request: Query call](#http-query) response and the following **additional** fields: - - (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. -- (optional) `certificate` (`blob`): a certificate, as specified in [Certification](#certification). - -If the IC nodes evaluating the query call do not compute the same query response (e.g., because they evaluated the query call on different states), -then the query call is rejected with the reject code `SYS_TRANSIENT` (2). If the query call is rejected with the reject code `SYS_TRANSIENT` (2), -then no signatures and certificate are contained in the response. - -:::note - -Currently only one IC node evaluates a query call and thus the query call cannot be rejected with the reject code `SYS_TRANSIENT` (2) -because the IC nodes evaluating the query call do not compute the same query response. However, query calls might be evaluated -by multiple IC nodes in the future and thus we specify the general behavior. - -::: - -Unless `requested_signatures` is null or set to `"none"` in the request and unless the query call is rejected with the reject code `SYS_TRANSIENT` (2), the response to a signed query call contains a non-empty list of signatures for the returned response produced by the individual IC nodes that computed the same returned response. Every such signature (whose type is denoted as `node-signature`) is a CBOR (see [CBOR](#cbor)) map with the following fields: +The response to a query call optionally contains a singleton list of signatures 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. @@ -779,29 +747,10 @@ Unless `requested_signatures` is null or set to `"none"` in the request and unle - `identity` (`principal`): the principal of the node producing the signature. -Unless `requested_certificate` is null or set to `"no"` in the request and unless the query call is rejected with the reject code `SYS_TRANSIENT` (2), the response to a signed query call contains a certificate containing all paths in the state tree with prefixes - -- `/time`. Always contained in the certificate. - -- `/subnet//canister_ranges`. Contained in the certificate if the certificate does not have a subnet delegation, as specified in [Delegation](#certification-delegation). - -- `/subnet//nodes//public_key`. Contained in the certificate if `` is the principal of a node in `signatures`. - -where `` characterizes the subnet to which the requested canister belongs. - -Given a query (the `content` map from the request body) `Q`, a response `R`, and a certificate `Cert` that is either - -- contained in the response, i.e., `Cert = R.certificate`, or - -- obtained by requesting the path `/subnet` in a **separate** read state request to `/api/v2/canister//read_state`, - -the following predicate describes when the returned response `R` is correctly signed by the nodes: +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//read_state`, the following predicate describes when the returned response `R` is correctly signed: verify_response(Q, R, Cert) - = (Q.requested_signatures = null ∨ Q.requested_signatures = "none" ∨ R.reject_code = SYS_TRANSIENT => R.signatures = null) ∧ - (Q.requested_signatures = "one" ∧ R.reject_code ≠ SYS_TRANSIENT => |R.signatures| = 1) ∧ - (Q.requested_certificate = null ∨ Q.requested_certificate = "no" ∨ R.reject_code = SYS_TRANSIENT => Cert = null) ∧ - verify_cert(Cert) ∧ + = verify_cert(Cert) ∧ ((Cert.delegation = NoDelegation ∧ SubnetId = RootSubnetId ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert) = Found Ranges) ∨ (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ effective_canister_id ∈ Ranges ∧ @@ -824,8 +773,6 @@ the following predicate describes when the returned response `R` is correctly si 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". -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. - ### Effective canister id {#http-effective-canister-id} The `` in the URL paths of requests is the *effective* destination of the request. @@ -2682,7 +2629,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`, `/api/v2/…/query`, and `/api/v2/…/signed_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. These are the synchronous read messages: @@ -2899,7 +2846,7 @@ Based on this abstract notion of the state, we can describe the behavior of the - Spontaneous transitions that model the internal behavior of the IC, by describing conditions on the state that allow the transition to happen, and the state after. -- Responses to reads (i.e. `/api/v2/…/read_state`, `/api/v2/…/query`, and `/api/v2/…/signed_query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. +- Responses to reads (i.e. `/api/v2/…/read_state`, and `/api/v2/…/query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. The state transitions are not complete with regard to error handling. For example, the behavior of sending a request to a non-existent canister is not specified here. For now, we trust implementors to make sensible decisions there. @@ -4613,7 +4560,7 @@ S with #### Query call -Canister query calls to `/api/v2/canister//query` and `/api/v2/canister//signed_query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen. +Canister query calls to `/api/v2/canister//query` can be executed directly. They can only be executed against non-empty canisters which have a status of `Running` and are also not frozen. In query and composite query methods evaluated on the target canister of the query call, a certificate is provided to the canister that is valid, contains a current state tree (or "recent enough"; the specification is currently vague about how old the certificate may be), and reveals the canister's [Certified Data](#system-api-certified-data). From 640e74f38f4bb8ee0ac57ffca4978a2a47742e1d Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 11 Jul 2023 18:06:43 +0200 Subject: [PATCH 24/38] simplify --- spec/index.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/index.md b/spec/index.md index 8b294dd48..8081fea73 100644 --- a/spec/index.md +++ b/spec/index.md @@ -517,7 +517,7 @@ The concrete mechanism that users use to send requests to the Internet Computer - At `/api/v2/canister//read_state` the user can read various information about the state of the Internet Computer. In particular, they can poll for the status of a call here. -- At `/api/v2/canister//query` the user can perform (synchronous, non-state-changing) query calls amended with signatures. +- At `/api/v2/canister//query` the user can perform (synchronous, non-state-changing) query calls. - At `/api/v2/status` the user can retrieve status information about the Internet Computer. @@ -2648,8 +2648,6 @@ These are the synchronous read messages: canister_id : CanisterId; method_name : Text; arg : Blob; - requested_certificate : Text; - requested_signatures : Text; } Signed delegations contain the (unsigned) delegation data in a nested record, next to the signature of that data. From cade854ac315bc43d8f80635650b064ca13f5ace Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 11 Jul 2023 18:09:01 +0200 Subject: [PATCH 25/38] simplify --- spec/index.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/index.md b/spec/index.md index 8081fea73..3d86a036a 100644 --- a/spec/index.md +++ b/spec/index.md @@ -2844,7 +2844,7 @@ Based on this abstract notion of the state, we can describe the behavior of the - Spontaneous transitions that model the internal behavior of the IC, by describing conditions on the state that allow the transition to happen, and the state after. -- Responses to reads (i.e. `/api/v2/…/read_state`, and `/api/v2/…/query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. +- Responses to reads (i.e. `/api/v2/…/read_state` and `/api/v2/…/query`). By definition, these do *not* change the state of the IC, and merely describe the response based on the read request (or query, respectively) and the current state. The state transitions are not complete with regard to error handling. For example, the behavior of sending a request to a non-existent canister is not specified here. For now, we trust implementors to make sensible decisions there. @@ -4665,17 +4665,17 @@ 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: , signatures: Sigs, certificate: Cert'} + {status: "rejected"; reject_code: RejectCode; reject_message: RejectMsg; error_code: , 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, certificate: Cert'} + {status: "replied"; reply: {arg: Res}, signatures: Sigs} -where the query `Q` and the response `R` satisfy the following: +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//read_state` satisfy the following: ```html -verify_responses(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" +verify_response(Q, R, Cert') ∧ lookup(["time"], Cert') = Found S.system_time // or "recent enough" ``` From f5738c1f713bfdb10cff85633987410be00b7d84 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 11 Jul 2023 18:11:13 +0200 Subject: [PATCH 26/38] simplify --- spec/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.md b/spec/index.md index 3d86a036a..f99feec0c 100644 --- a/spec/index.md +++ b/spec/index.md @@ -725,7 +725,7 @@ If the query call resulted in a reply, the response is a CBOR (see [CBOR](#cbor) - `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. -- (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. +- (optional) `signatures` (`[+ node-signature]`): a singleton list of node signatures for the returned query response. If the call resulted in a reject, the response is a CBOR map with the following fields: @@ -737,7 +737,7 @@ If the call resulted in a reject, the response is a CBOR map with the following - `error_code` (`text`): an optional implementation-specific textual error code (see [Error codes](#error-codes)). -- (optional) `signatures` (`[+ node-signature]`): a non-empty list of node signatures for the returned query response. +- (optional) `signatures` (`[+ node-signature]`): a singleton list of node signatures for the returned query response. The response to a query call optionally contains a singleton list of signatures 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: From 2ae11ef4219d32084209244596887c86a67621fb Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Mon, 17 Jul 2023 20:43:18 +0200 Subject: [PATCH 27/38] replace CanisterSignedQuery by CanisterQuery in formal text --- spec/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/index.md b/spec/index.md index f99feec0c..45fe371f7 100644 --- a/spec/index.md +++ b/spec/index.md @@ -2641,7 +2641,7 @@ These are the synchronous read messages: sender : UserId; paths : List(Path); } - | CanisterSignedQuery = { + | CanisterQuery = { nonce : Blob; ingress_expiry : Nat; sender : UserId; @@ -4654,7 +4654,7 @@ Conditions ```html -E.content = CanisterSignedQuery Q +E.content = CanisterQuery Q Q.canister_id ∈ verify_envelope(E, Q.sender, S.system_time) is_effective_canister_id(E.content, ECID) S.system_time <= Q.ingress_expiry From 26b80cdf7f3496621101ec4a801e93f1f555a8d4 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Mon, 17 Jul 2023 20:47:20 +0200 Subject: [PATCH 28/38] singleton -> containing one; a note on signatures being a list --- spec/index.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/spec/index.md b/spec/index.md index 45fe371f7..de6a4862f 100644 --- a/spec/index.md +++ b/spec/index.md @@ -725,7 +725,7 @@ If the query call resulted in a reply, the response is a CBOR (see [CBOR](#cbor) - `reply`: a CBOR map with the field `arg` (`blob`) which contains the reply data. -- (optional) `signatures` (`[+ node-signature]`): a singleton list of node signatures for the returned query response. +- (optional) `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: @@ -737,9 +737,16 @@ If the call resulted in a reject, the response is a CBOR map with the following - `error_code` (`text`): an optional implementation-specific textual error code (see [Error codes](#error-codes)). -- (optional) `signatures` (`[+ node-signature]`): a singleton list of node signatures for the returned query response. +- (optional) `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. -The response to a query call optionally contains a singleton list of signatures 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: +:::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. From 11f599768fb2283a5f78f65c566ddf8b8730d3a1 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 25 Jul 2023 16:29:18 +0200 Subject: [PATCH 29/38] add note on what recent enough means --- spec/index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/index.md b/spec/index.md index 192b08de1..48ed7b9e7 100644 --- a/spec/index.md +++ b/spec/index.md @@ -780,6 +780,12 @@ Given a query (the `content` map from the request body) `Q`, a response `R`, and 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". +:::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. + +::: + ### Effective canister id {#http-effective-canister-id} The `` in the URL paths of requests is the *effective* destination of the request. From e625cf26bd748bca38ba9dc078e61dd5538fc28e Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 9 Aug 2023 11:49:13 +0200 Subject: [PATCH 30/38] nodes -> node --- spec/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/index.md b/spec/index.md index 48ed7b9e7..8e7e90648 100644 --- a/spec/index.md +++ b/spec/index.md @@ -445,7 +445,7 @@ Because this uses the lexicographic ordering of princpials, and the byte disting ::: -- `/subnet//nodes//public_key` (blob) +- `/subnet//node//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 `` belonging to the subnet with principal ``. @@ -647,7 +647,7 @@ The HTTP response to this request consists of a CBOR (see [CBOR](#cbor)) map wit The returned certificate reveals all values whose path has a requested path as a prefix except for -- paths with prefix `/subnet//nodes`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at the path `/subnet//canister_ranges` in the state tree. +- paths with prefix `/subnet//node`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at the path `/subnet//canister_ranges` in the state tree. The returned certificate also always reveals `/time`, even if not explicitly requested. @@ -762,7 +762,7 @@ Given a query (the `content` map from the request body) `Q`, a response `R`, and (SubnetId = Cert.delegation.subnet_id ∧ lookup(["subnet",SubnetId,"canister_ranges"], Cert.delegation.certificate) = Found Ranges)) ∧ effective_canister_id ∈ Ranges ∧ ∀ {timestamp: T, signature: Sig, identity: NodeId} ∈ R.signatures. - lookup(["subnet",SubnetId,"nodes",NodeId,"public_key"], Cert) = Found PK ∧ + 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", @@ -4748,7 +4748,7 @@ where `state_tree` constructs a labeled tree from the IC state `S` and the (so f state_tree(S) = { "time": S.system_time; - "subnet": { subnet_id : { "public_key" : subnet_pk, "canister_ranges" : subnet_ranges, "nodes": { node_id : { "public_key" : node_pk } | (node_id, node_pk) ∈ subnet_nodes } } | (subnet_id, subnet_pk, subnet_ranges, subnet_nodes) ∈ 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 : From 3103163d2599c20dfd02e27a13bb285a3cc9f217 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Sat, 19 Aug 2023 08:49:19 +0200 Subject: [PATCH 31/38] make signatures in query responses non-optional --- spec/_attachments/requests.cddl | 4 ++-- spec/index.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/_attachments/requests.cddl b/spec/_attachments/requests.cddl index 5fee31431..214430d43 100644 --- a/spec/_attachments/requests.cddl +++ b/spec/_attachments/requests.cddl @@ -66,13 +66,13 @@ query-content = { query-response = tagged<{ status: "replied" reply: call-reply - ? signatures: [+ node-signature] + signatures: [+ node-signature] // status: "rejected" reject_code: unsigned reject_message: text ? error_code: text - ? signatures: [+ node-signature] + signatures: [+ node-signature] }> node-signature = { diff --git a/spec/index.md b/spec/index.md index 7a673a7f4..47c998713 100644 --- a/spec/index.md +++ b/spec/index.md @@ -737,7 +737,7 @@ If the query call resulted in a reply, the response is a CBOR (see [CBOR](#cbor) - `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. +- `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: @@ -749,7 +749,7 @@ If the call resulted in a reject, the response is a CBOR map with the following - `error_code` (`text`): an optional implementation-specific textual error code (see [Error codes](#error-codes)). -- (optional) `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. +- `signatures` (`[+ node-signature]`): a list containing one node signature for the returned query response. :::note From 6ac4e03145045ae7a9b68a415d28a695e22460e5 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Sat, 19 Aug 2023 08:52:30 +0200 Subject: [PATCH 32/38] drop a note on optional signatures --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index 47c998713..abde0ae3a 100644 --- a/spec/index.md +++ b/spec/index.md @@ -758,7 +758,7 @@ 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: +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. From 5e673b89f64d28eb453bdb723fea5a0313d2083a Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Tue, 22 Aug 2023 14:07:35 +0200 Subject: [PATCH 33/38] whitelist node paths in read_state requests --- spec/index.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index abde0ae3a..c43b10c9e 100644 --- a/spec/index.md +++ b/spec/index.md @@ -667,7 +667,7 @@ All requested paths must have the following form: - `/time`. Can always be requested. -- `/subnet`, `/subnet/`, `/subnet//public_key`, `/subnet//canister_ranges`. Can always be requested. +- `/subnet`, `/subnet/`, `/subnet//public_key`, `/subnet//canister_ranges`, `/subnet//node`, `/subnet//node/`, `/subnet//node//public_key`. Can always be requested. - `/request_status/`, `/request_status//status`, `/request_status//reply`, `/request_status//reject_code`, `/request_status//reject_message`, `/request_status//error_code`. Can be requested if no path with such a prefix exists in the state tree or @@ -4740,6 +4740,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"]) = From bf346e56059d20fdbfbc67dda65b14c829d3882e Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:19:24 +0200 Subject: [PATCH 34/38] Update spec/index.md Co-authored-by: Thomas Locher --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index c43b10c9e..930449b25 100644 --- a/spec/index.md +++ b/spec/index.md @@ -794,7 +794,7 @@ where `RootSubnetId` is the a priori known principal of the root subnet. Moreove :::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. +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. ::: From f8da3de616aa0921b47bda8abacb1f7433b1aac9 Mon Sep 17 00:00:00 2001 From: mraszyk <31483726+mraszyk@users.noreply.github.com> Date: Fri, 1 Sep 2023 12:24:49 +0200 Subject: [PATCH 35/38] Update spec/index.md Co-authored-by: Thomas Locher --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index 930449b25..20edcd1b7 100644 --- a/spec/index.md +++ b/spec/index.md @@ -729,7 +729,7 @@ In order to make a query call to a canister, the user makes a POST request to `/ - `arg` (`blob`): Argument to pass to the canister method. -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. +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. If the query call resulted in a reply, the response is a CBOR (see [CBOR](#cbor)) map with the following fields: From 543745b7720371eba731cb4ff129f0a929826375 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Fri, 1 Sep 2023 12:27:00 +0200 Subject: [PATCH 36/38] typo --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index 20edcd1b7..d0af2ca67 100644 --- a/spec/index.md +++ b/spec/index.md @@ -2663,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. These are the synchronous read messages: From d77e24d08421967c508838b227a32246046da21a Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Fri, 1 Sep 2023 12:27:40 +0200 Subject: [PATCH 37/38] reformulation --- spec/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/index.md b/spec/index.md index d0af2ca67..79131adc6 100644 --- a/spec/index.md +++ b/spec/index.md @@ -648,7 +648,7 @@ The HTTP response to this request consists of a CBOR (see [CBOR](#cbor)) map wit The returned certificate reveals all values whose path has a requested path as a prefix except for -- paths with prefix `/subnet//node`. Only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at the path `/subnet//canister_ranges` in the state tree. +- paths with prefix `/subnet//node` which are only contained in the returned certificate if `` belongs to the canister ranges of the subnet ``, i.e., if `` belongs to the value at the path `/subnet//canister_ranges` in the state tree. The returned certificate also always reveals `/time`, even if not explicitly requested. From f3971dc7f0d2bdca99f00ffdeaa8e6a802a97e45 Mon Sep 17 00:00:00 2001 From: Martin Raszyk Date: Wed, 13 Sep 2023 16:33:45 +0200 Subject: [PATCH 38/38] update changelog --- spec/_attachments/interface-spec-changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/_attachments/interface-spec-changelog.md b/spec/_attachments/interface-spec-changelog.md index cacee70dc..396d9052c 100644 --- a/spec/_attachments/interface-spec-changelog.md +++ b/spec/_attachments/interface-spec-changelog.md @@ -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.