canic_core/workflow/rpc/capability/
proof.rs1use crate::{
2 cdk::types::Principal,
3 dto::{
4 capability::{CapabilityProof, CapabilityProofBlob, DelegatedGrantProof},
5 error::Error,
6 rpc::{CreateCanisterParent, Request},
7 },
8 ops::{
9 ic::IcOps, storage::children::CanisterChildrenOps,
10 storage::registry::subnet::SubnetRegistryOps,
11 },
12};
13use candid::{decode_one, encode_one};
14use std::convert::TryFrom;
15
16pub(super) fn verify_root_structural_proof(capability: &Request) -> Result<(), Error> {
20 let caller = IcOps::msg_caller();
21
22 if SubnetRegistryOps::get(caller).is_none() {
23 return Err(Error::forbidden(
24 "structural proof requires caller to be registered in subnet registry",
25 ));
26 }
27
28 match capability {
29 Request::Cycles(_) => Ok(()),
30 Request::CreateCanister(request) => verify_root_structural_create(request),
31 Request::UpgradeCanister(request) => {
32 verify_root_structural_child_target(caller, request.canister_pid, "upgrade")
33 }
34 Request::RecycleCanister(request) => {
35 verify_root_structural_child_target(caller, request.canister_pid, "recycle")
36 }
37 Request::IssueRoleAttestation(_) | Request::IssueInternalInvocationProof(_) => {
38 Err(Error::forbidden(
39 "structural proof is only supported for root cycles, child provision, child upgrade, and child recycle capabilities",
40 ))
41 }
42 }
43}
44
45fn verify_root_structural_create(
46 request: &crate::dto::rpc::CreateCanisterRequest,
47) -> Result<(), Error> {
48 if matches!(&request.parent, CreateCanisterParent::ThisCanister) {
49 return Ok(());
50 }
51
52 Err(Error::forbidden(
53 "structural provision proof requires parent=ThisCanister",
54 ))
55}
56
57fn verify_root_structural_child_target(
58 caller: Principal,
59 target_pid: Principal,
60 operation: &str,
61) -> Result<(), Error> {
62 let target = SubnetRegistryOps::get(target_pid).ok_or_else(|| {
63 Error::forbidden(format!(
64 "structural proof requires registered {operation} target"
65 ))
66 })?;
67 if target.parent_pid != Some(caller) {
68 return Err(Error::forbidden(format!(
69 "structural proof requires {operation} target to be a direct child of caller"
70 )));
71 }
72 Ok(())
73}
74
75pub(super) fn verify_nonroot_structural_cycles_proof() -> Result<(), Error> {
79 let caller = IcOps::msg_caller();
80
81 if !CanisterChildrenOps::contains_pid(&caller) {
82 return Err(Error::forbidden(
83 "structural proof requires caller to be a direct child of receiver",
84 ));
85 }
86
87 Ok(())
88}
89
90pub(super) fn verify_capability_hash_binding(
94 target_canister: Principal,
95 capability_version: u16,
96 capability: &Request,
97 capability_hash: [u8; 32],
98) -> Result<(), Error> {
99 let expected = super::root_capability_hash(target_canister, capability_version, capability)?;
100 if capability_hash != expected {
101 return Err(Error::invalid(
102 "capability_hash does not match capability payload",
103 ));
104 }
105
106 Ok(())
107}
108
109pub(super) fn encode_delegated_grant_blob(
113 proof: &DelegatedGrantProof,
114) -> Result<CapabilityProofBlob, Error> {
115 Ok(CapabilityProofBlob {
116 proof_version: proof.proof_version,
117 capability_hash: proof.capability_hash,
118 payload: encode_one(proof).map_err(|err| {
119 Error::internal(format!("failed to encode delegated grant proof: {err}"))
120 })?,
121 })
122}
123
124pub(super) fn decode_delegated_grant_blob(
126 blob: &CapabilityProofBlob,
127) -> Result<DelegatedGrantProof, Error> {
128 let proof: DelegatedGrantProof = decode_one(&blob.payload)
129 .map_err(|err| Error::invalid(format!("failed to decode delegated grant proof: {err}")))?;
130
131 if proof.proof_version != blob.proof_version {
132 return Err(Error::invalid(
133 "delegated grant proof_version does not match wire header",
134 ));
135 }
136 if proof.capability_hash != blob.capability_hash {
137 return Err(Error::invalid(
138 "delegated grant capability_hash does not match wire header",
139 ));
140 }
141
142 Ok(proof)
143}
144
145impl TryFrom<DelegatedGrantProof> for CapabilityProof {
146 type Error = Error;
147
148 fn try_from(value: DelegatedGrantProof) -> Result<Self, Self::Error> {
149 Ok(Self::DelegatedGrant(encode_delegated_grant_blob(&value)?))
150 }
151}