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::{ic::IcOps, storage::registry::subnet::SubnetRegistryOps},
11};
12use candid::{decode_one, encode_one};
13use std::convert::TryFrom;
14
15pub(super) fn verify_root_structural_proof(capability: &Request) -> Result<(), Error> {
19 let caller = IcOps::msg_caller();
20
21 if SubnetRegistryOps::get(caller).is_none() {
22 return Err(Error::forbidden(
23 "structural proof requires caller to be registered in subnet registry",
24 ));
25 }
26
27 if capability.family() == RequestFamily::RequestCycles {
28 return Ok(());
29 }
30
31 if let Some(request) = capability.upgrade_request() {
32 let target = SubnetRegistryOps::get(request.canister_pid).ok_or_else(|| {
33 Error::forbidden("structural proof requires registered upgrade target")
34 })?;
35 if target.parent_pid != Some(caller) {
36 return Err(Error::forbidden(
37 "structural proof requires upgrade target to be a direct child of caller",
38 ));
39 }
40 return Ok(());
41 }
42
43 Err(Error::forbidden(
44 "structural proof is only supported for root cycles and upgrade capabilities",
45 ))
46}
47
48pub(super) fn verify_capability_hash_binding(
52 target_canister: Principal,
53 capability_version: u16,
54 capability: &Request,
55 capability_hash: [u8; 32],
56) -> Result<(), Error> {
57 let expected = super::root_capability_hash(target_canister, capability_version, capability)?;
58 if capability_hash != expected {
59 return Err(Error::invalid(
60 "capability_hash does not match capability payload",
61 ));
62 }
63
64 Ok(())
65}
66
67pub(super) fn encode_role_attestation_blob(
71 proof: &RoleAttestationProof,
72) -> Result<CapabilityProofBlob, Error> {
73 Ok(CapabilityProofBlob {
74 proof_version: proof.proof_version,
75 capability_hash: proof.capability_hash,
76 payload: encode_one(proof).map_err(|err| {
77 Error::internal(format!("failed to encode role attestation proof: {err}"))
78 })?,
79 })
80}
81
82pub(super) fn decode_role_attestation_blob(
84 blob: &CapabilityProofBlob,
85) -> Result<RoleAttestationProof, Error> {
86 let proof: RoleAttestationProof = decode_one(&blob.payload)
87 .map_err(|err| Error::invalid(format!("failed to decode role attestation proof: {err}")))?;
88
89 if proof.proof_version != blob.proof_version {
90 return Err(Error::invalid(
91 "role attestation proof_version does not match wire header",
92 ));
93 }
94 if proof.capability_hash != blob.capability_hash {
95 return Err(Error::invalid(
96 "role attestation capability_hash does not match wire header",
97 ));
98 }
99
100 Ok(proof)
101}
102
103pub(super) fn encode_delegated_grant_blob(
105 proof: &DelegatedGrantProof,
106) -> Result<CapabilityProofBlob, Error> {
107 Ok(CapabilityProofBlob {
108 proof_version: proof.proof_version,
109 capability_hash: proof.capability_hash,
110 payload: encode_one(proof).map_err(|err| {
111 Error::internal(format!("failed to encode delegated grant proof: {err}"))
112 })?,
113 })
114}
115
116pub(super) fn decode_delegated_grant_blob(
118 blob: &CapabilityProofBlob,
119) -> Result<DelegatedGrantProof, Error> {
120 let proof: DelegatedGrantProof = decode_one(&blob.payload)
121 .map_err(|err| Error::invalid(format!("failed to decode delegated grant proof: {err}")))?;
122
123 if proof.proof_version != blob.proof_version {
124 return Err(Error::invalid(
125 "delegated grant proof_version does not match wire header",
126 ));
127 }
128 if proof.capability_hash != blob.capability_hash {
129 return Err(Error::invalid(
130 "delegated grant capability_hash does not match wire header",
131 ));
132 }
133
134 Ok(proof)
135}
136
137impl TryFrom<RoleAttestationProof> for CapabilityProof {
138 type Error = Error;
139
140 fn try_from(value: RoleAttestationProof) -> Result<Self, Self::Error> {
141 Ok(Self::RoleAttestation(encode_role_attestation_blob(&value)?))
142 }
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}