penumbra_sdk_custody/
policy.rs1use std::collections::HashSet;
4
5use penumbra_sdk_keys::Address;
6use penumbra_sdk_proto::{
7 core::{
8 component::{
9 governance::v1::ValidatorVoteBody as ProtoValidatorVoteBody,
10 stake::v1::Validator as ProtoValidator,
11 },
12 transaction::v1::TransactionPlan as ProtoTransactionPlan,
13 },
14 Message as _,
15};
16use penumbra_sdk_transaction::plan::ActionPlan;
17use serde::{Deserialize, Serialize};
18
19use crate::{
20 AuthorizeRequest, AuthorizeValidatorDefinitionRequest, AuthorizeValidatorVoteRequest,
21 PreAuthorization,
22};
23
24pub trait Policy {
26 fn check_transaction(&self, request: &AuthorizeRequest) -> anyhow::Result<()>;
28
29 fn check_validator_definition(
31 &self,
32 _request: &AuthorizeValidatorDefinitionRequest,
33 ) -> anyhow::Result<()>;
34
35 fn check_validator_vote(&self, _request: &AuthorizeValidatorVoteRequest) -> anyhow::Result<()>;
37}
38
39#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
48#[serde(tag = "type")]
49pub enum AuthPolicy {
50 DestinationAllowList {
53 #[serde(with = "address_as_string")]
54 allowed_destination_addresses: Vec<Address>,
55 },
56 OnlyIbcRelay,
62 PreAuthorization(PreAuthorizationPolicy),
64}
65
66#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
68#[serde(tag = "method")]
74pub enum PreAuthorizationPolicy {
75 Ed25519 {
76 required_signatures: u32,
80 #[serde(with = "ed25519_vec_base64")]
82 allowed_signers: Vec<ed25519_consensus::VerificationKey>,
83 },
84}
85
86impl PreAuthorizationPolicy {
87 fn check_pre_authorizations(
88 &self,
89 pre_authorizations: &[PreAuthorization],
90 signed_data: impl AsRef<[u8]>,
91 ) -> anyhow::Result<()> {
92 let signed_data = signed_data.as_ref();
93 match self {
94 PreAuthorizationPolicy::Ed25519 {
95 required_signatures,
96 allowed_signers,
97 } => {
98 #[allow(clippy::unnecessary_filter_map)]
99 let ed25519_pre_auths =
100 pre_authorizations
101 .iter()
102 .filter_map(|pre_auth| match pre_auth {
103 PreAuthorization::Ed25519(pre_auth) => Some(pre_auth),
104 });
106
107 let mut allowed_signers = allowed_signers.iter().cloned().collect::<HashSet<_>>();
108 let mut seen_signers = HashSet::new();
109
110 for pre_auth in ed25519_pre_auths {
111 if let Some(signer) = allowed_signers.take(&pre_auth.vk) {
114 pre_auth.verify(signed_data)?;
115 seen_signers.insert(signer);
116 }
117 }
118
119 if seen_signers.len() < *required_signatures as usize {
120 anyhow::bail!(
121 "required {} pre-authorization signatures but only saw {}",
122 required_signatures,
123 seen_signers.len(),
124 );
125 }
126 Ok(())
127 }
128 }
129 }
130}
131
132mod address_as_string {
133 use std::str::FromStr;
134
135 use penumbra_sdk_keys::Address;
136
137 pub fn serialize<S: serde::Serializer>(
138 addresses: &[Address],
139 serializer: S,
140 ) -> Result<S::Ok, S::Error> {
141 use serde::Serialize;
142 let mut string_addresses = Vec::with_capacity(addresses.len());
143 for address in addresses {
144 string_addresses.push(address.to_string());
145 }
146 string_addresses.serialize(serializer)
147 }
148 pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Address>, D::Error>
149 where
150 D: serde::Deserializer<'de>,
151 {
152 use serde::Deserialize;
153 let string_addresses: Vec<String> = Vec::deserialize(deserializer)?;
154 let mut addresses = Vec::with_capacity(string_addresses.len());
155 for string_address in string_addresses {
156 let address = Address::from_str(&string_address).map_err(serde::de::Error::custom)?;
157 addresses.push(address);
158 }
159 Ok(addresses)
160 }
161}
162
163mod ed25519_vec_base64 {
169 use base64::prelude::*;
170
171 pub fn serialize<S: serde::Serializer>(
172 keys: &[ed25519_consensus::VerificationKey],
173 serializer: S,
174 ) -> Result<S::Ok, S::Error> {
175 use serde::Serialize;
176 let mut base64_keys = Vec::with_capacity(keys.len());
177 for key in keys {
178 base64_keys.push(BASE64_STANDARD.encode(key.as_bytes()));
179 }
180 base64_keys.serialize(serializer)
181 }
182 pub fn deserialize<'de, D>(
183 deserializer: D,
184 ) -> Result<Vec<ed25519_consensus::VerificationKey>, D::Error>
185 where
186 D: serde::Deserializer<'de>,
187 {
188 use serde::Deserialize;
189 let base64_keys: Vec<String> = Vec::deserialize(deserializer)?;
190 let mut vks = Vec::with_capacity(base64_keys.len());
191 for base64_key in base64_keys {
192 let bytes = BASE64_STANDARD
193 .decode(base64_key)
194 .map_err(serde::de::Error::custom)?;
195 let vk = ed25519_consensus::VerificationKey::try_from(bytes.as_slice())
196 .map_err(serde::de::Error::custom)?;
197 vks.push(vk);
198 }
199 Ok(vks)
200 }
201}
202
203impl Policy for AuthPolicy {
204 fn check_transaction(&self, request: &AuthorizeRequest) -> anyhow::Result<()> {
205 let plan = &request.plan;
206 match self {
207 AuthPolicy::DestinationAllowList {
208 allowed_destination_addresses,
209 } => {
210 for output in plan.output_plans() {
211 if !allowed_destination_addresses.contains(&output.dest_address) {
212 anyhow::bail!("output {:?} has dest_address not in allow list", output);
213 }
214 }
215 for swap in plan.swap_plans() {
216 if !allowed_destination_addresses.contains(&swap.swap_plaintext.claim_address) {
217 anyhow::bail!("swap {:?} has claim_address not in allow list", swap);
218 }
219 }
220 Ok(())
221 }
222 AuthPolicy::OnlyIbcRelay => {
223 for action in &plan.actions {
224 match action {
225 ActionPlan::Spend { .. }
226 | ActionPlan::Output { .. }
227 | ActionPlan::IbcAction { .. } => {}
228 _ => {
229 anyhow::bail!("action {:?} not allowed by OnlyRelay policy", action);
230 }
231 }
232 }
233 Ok(())
234 }
235 AuthPolicy::PreAuthorization(policy) => policy.check_transaction(request),
236 }
237 }
238
239 fn check_validator_definition(
240 &self,
241 _request: &AuthorizeValidatorDefinitionRequest,
242 ) -> anyhow::Result<()> {
243 anyhow::bail!("validator definitions are not allowed by this policy")
244 }
245
246 fn check_validator_vote(&self, _request: &AuthorizeValidatorVoteRequest) -> anyhow::Result<()> {
247 anyhow::bail!("validator votes are not allowed by this policy")
248 }
249}
250
251impl Policy for PreAuthorizationPolicy {
252 fn check_transaction(&self, request: &AuthorizeRequest) -> anyhow::Result<()> {
253 self.check_pre_authorizations(
254 &request.pre_authorizations,
255 ProtoTransactionPlan::from(request.plan.clone()).encode_to_vec(),
256 )
257 }
258
259 fn check_validator_definition(
260 &self,
261 request: &AuthorizeValidatorDefinitionRequest,
262 ) -> anyhow::Result<()> {
263 self.check_pre_authorizations(
264 &request.pre_authorizations,
265 ProtoValidator::from(request.validator_definition.clone()).encode_to_vec(),
266 )
267 }
268
269 fn check_validator_vote(&self, request: &AuthorizeValidatorVoteRequest) -> anyhow::Result<()> {
270 self.check_pre_authorizations(
271 &request.pre_authorizations,
272 ProtoValidatorVoteBody::from(request.validator_vote.clone()).encode_to_vec(),
273 )
274 }
275}