canic_core/api/rpc/capability/
proof.rs1use crate::{
2 cdk::types::Principal,
3 dto::{
4 capability::{
5 CapabilityProof, CapabilityProofBlob, DelegatedGrantProof, RoleAttestationProof,
6 },
7 error::Error,
8 rpc::{Request, RequestFamily},
9 },
10 ops::{
11 ic::IcOps, storage::children::CanisterChildrenOps,
12 storage::registry::subnet::SubnetRegistryOps,
13 },
14};
15use candid::{decode_one, encode_one};
16use std::convert::TryFrom;
17
18pub(super) fn verify_root_structural_proof(capability: &Request) -> Result<(), Error> {
22 let caller = IcOps::msg_caller();
23
24 if SubnetRegistryOps::get(caller).is_none() {
25 return Err(Error::forbidden(
26 "structural proof requires caller to be registered in subnet registry",
27 ));
28 }
29
30 if capability.family() == RequestFamily::RequestCycles {
31 return Ok(());
32 }
33
34 if let Some(request) = capability.upgrade_request() {
35 let target = SubnetRegistryOps::get(request.canister_pid).ok_or_else(|| {
36 Error::forbidden("structural proof requires registered upgrade target")
37 })?;
38 if target.parent_pid != Some(caller) {
39 return Err(Error::forbidden(
40 "structural proof requires upgrade target to be a direct child of caller",
41 ));
42 }
43 return Ok(());
44 }
45
46 Err(Error::forbidden(
47 "structural proof is only supported for root cycles and upgrade capabilities",
48 ))
49}
50
51pub(super) fn verify_nonroot_structural_cycles_proof() -> Result<(), Error> {
55 let caller = IcOps::msg_caller();
56
57 if !CanisterChildrenOps::contains_pid(&caller) {
58 return Err(Error::forbidden(
59 "structural proof requires caller to be a direct child of receiver",
60 ));
61 }
62
63 Ok(())
64}
65
66pub(super) fn verify_capability_hash_binding(
70 target_canister: Principal,
71 capability_version: u16,
72 capability: &Request,
73 capability_hash: [u8; 32],
74) -> Result<(), Error> {
75 let expected = super::root_capability_hash(target_canister, capability_version, capability)?;
76 if capability_hash != expected {
77 return Err(Error::invalid(
78 "capability_hash does not match capability payload",
79 ));
80 }
81
82 Ok(())
83}
84
85pub(super) fn encode_role_attestation_blob(
89 proof: &RoleAttestationProof,
90) -> Result<CapabilityProofBlob, Error> {
91 Ok(CapabilityProofBlob {
92 proof_version: proof.proof_version,
93 capability_hash: proof.capability_hash,
94 payload: encode_one(proof).map_err(|err| {
95 Error::internal(format!("failed to encode role attestation proof: {err}"))
96 })?,
97 })
98}
99
100pub(super) fn decode_role_attestation_blob(
102 blob: &CapabilityProofBlob,
103) -> Result<RoleAttestationProof, Error> {
104 let proof: RoleAttestationProof = decode_one(&blob.payload)
105 .map_err(|err| Error::invalid(format!("failed to decode role attestation proof: {err}")))?;
106
107 if proof.proof_version != blob.proof_version {
108 return Err(Error::invalid(
109 "role attestation proof_version does not match wire header",
110 ));
111 }
112 if proof.capability_hash != blob.capability_hash {
113 return Err(Error::invalid(
114 "role attestation capability_hash does not match wire header",
115 ));
116 }
117
118 Ok(proof)
119}
120
121pub(super) fn encode_delegated_grant_blob(
123 proof: &DelegatedGrantProof,
124) -> Result<CapabilityProofBlob, Error> {
125 Ok(CapabilityProofBlob {
126 proof_version: proof.proof_version,
127 capability_hash: proof.capability_hash,
128 payload: encode_one(proof).map_err(|err| {
129 Error::internal(format!("failed to encode delegated grant proof: {err}"))
130 })?,
131 })
132}
133
134pub(super) fn decode_delegated_grant_blob(
136 blob: &CapabilityProofBlob,
137) -> Result<DelegatedGrantProof, Error> {
138 let proof: DelegatedGrantProof = decode_one(&blob.payload)
139 .map_err(|err| Error::invalid(format!("failed to decode delegated grant proof: {err}")))?;
140
141 if proof.proof_version != blob.proof_version {
142 return Err(Error::invalid(
143 "delegated grant proof_version does not match wire header",
144 ));
145 }
146 if proof.capability_hash != blob.capability_hash {
147 return Err(Error::invalid(
148 "delegated grant capability_hash does not match wire header",
149 ));
150 }
151
152 Ok(proof)
153}
154
155impl TryFrom<RoleAttestationProof> for CapabilityProof {
156 type Error = Error;
157
158 fn try_from(value: RoleAttestationProof) -> Result<Self, Self::Error> {
159 Ok(Self::RoleAttestation(encode_role_attestation_blob(&value)?))
160 }
161}
162
163impl TryFrom<DelegatedGrantProof> for CapabilityProof {
164 type Error = Error;
165
166 fn try_from(value: DelegatedGrantProof) -> Result<Self, Self::Error> {
167 Ok(Self::DelegatedGrant(encode_delegated_grant_blob(&value)?))
168 }
169}