penumbra_sdk_custody/
soft_kms.rs1use decaf377_rdsa::{Signature, SpendAuth};
5use penumbra_sdk_proto::{
6 core::component::{
7 governance::v1::ValidatorVoteBody as ProtoValidatorVoteBody,
8 stake::v1::Validator as ProtoValidator,
9 },
10 custody::v1::{self as pb, AuthorizeResponse},
11 Message as _,
12};
13use penumbra_sdk_transaction::AuthorizationData;
14use rand_core::OsRng;
15use tonic::{async_trait, Request, Response, Status};
16
17use crate::{
18 policy::Policy, AuthorizeRequest, AuthorizeValidatorDefinitionRequest,
19 AuthorizeValidatorVoteRequest,
20};
21
22mod config;
23
24pub use config::Config;
25
26pub struct SoftKms {
29 config: Config,
30}
31
32impl SoftKms {
33 pub fn new(config: Config) -> Self {
35 Self { config }
36 }
37
38 #[tracing::instrument(skip(self, request), name = "softhsm_sign")]
40 pub fn sign(&self, request: &AuthorizeRequest) -> anyhow::Result<AuthorizationData> {
41 tracing::debug!(?request.plan);
42
43 for policy in &self.config.auth_policy {
44 policy.check_transaction(request)?;
45 }
46
47 Ok(request.plan.authorize(OsRng, &self.config.spend_key)?)
48 }
49
50 #[tracing::instrument(skip(self, request), name = "softhsm_sign_validator_definition")]
52 pub fn sign_validator_definition(
53 &self,
54 request: &AuthorizeValidatorDefinitionRequest,
55 ) -> anyhow::Result<Signature<SpendAuth>> {
56 tracing::debug!(?request.validator_definition);
57
58 for policy in &self.config.auth_policy {
59 policy.check_validator_definition(request)?;
60 }
61
62 let protobuf_serialized: ProtoValidator = request.validator_definition.clone().into();
63 let validator_definition_bytes = protobuf_serialized.encode_to_vec();
64
65 Ok(self
66 .config
67 .spend_key
68 .spend_auth_key()
69 .sign(OsRng, &validator_definition_bytes))
70 }
71
72 #[tracing::instrument(skip(self, request), name = "softhsm_sign_validator_vote")]
74 pub fn sign_validator_vote(
75 &self,
76 request: &AuthorizeValidatorVoteRequest,
77 ) -> anyhow::Result<Signature<SpendAuth>> {
78 tracing::debug!(?request.validator_vote);
79
80 for policy in &self.config.auth_policy {
81 policy.check_validator_vote(request)?;
82 }
83
84 let protobuf_serialized: ProtoValidatorVoteBody = request.validator_vote.clone().into();
85 let validator_vote_bytes = protobuf_serialized.encode_to_vec();
86
87 Ok(self
88 .config
89 .spend_key
90 .spend_auth_key()
91 .sign(OsRng, &validator_vote_bytes))
92 }
93}
94
95#[async_trait]
96impl pb::custody_service_server::CustodyService for SoftKms {
97 async fn authorize(
98 &self,
99 request: Request<pb::AuthorizeRequest>,
100 ) -> Result<Response<AuthorizeResponse>, Status> {
101 let request = request
102 .into_inner()
103 .try_into()
104 .map_err(|e: anyhow::Error| Status::invalid_argument(e.to_string()))?;
105
106 let authorization_data = self
107 .sign(&request)
108 .map_err(|e| Status::unauthenticated(format!("{e:#}")))?;
109
110 let authorization_response = AuthorizeResponse {
111 data: Some(authorization_data.into()),
112 };
113
114 Ok(Response::new(authorization_response))
115 }
116
117 async fn authorize_validator_definition(
118 &self,
119 request: Request<pb::AuthorizeValidatorDefinitionRequest>,
120 ) -> Result<Response<pb::AuthorizeValidatorDefinitionResponse>, Status> {
121 let request = request
122 .into_inner()
123 .try_into()
124 .map_err(|e: anyhow::Error| Status::invalid_argument(e.to_string()))?;
125
126 let validator_definition_auth = self
127 .sign_validator_definition(&request)
128 .map_err(|e| Status::unauthenticated(format!("{e:#}")))?;
129
130 let authorization_response = pb::AuthorizeValidatorDefinitionResponse {
131 validator_definition_auth: Some(validator_definition_auth.into()),
132 };
133
134 Ok(Response::new(authorization_response))
135 }
136
137 async fn authorize_validator_vote(
138 &self,
139 request: Request<pb::AuthorizeValidatorVoteRequest>,
140 ) -> Result<Response<pb::AuthorizeValidatorVoteResponse>, Status> {
141 let request = request
142 .into_inner()
143 .try_into()
144 .map_err(|e: anyhow::Error| Status::invalid_argument(e.to_string()))?;
145
146 let validator_vote_auth = self
147 .sign_validator_vote(&request)
148 .map_err(|e| Status::unauthenticated(format!("{e:#}")))?;
149
150 let authorization_response = pb::AuthorizeValidatorVoteResponse {
151 validator_vote_auth: Some(validator_vote_auth.into()),
152 };
153
154 Ok(Response::new(authorization_response))
155 }
156
157 async fn export_full_viewing_key(
158 &self,
159 _request: Request<pb::ExportFullViewingKeyRequest>,
160 ) -> Result<Response<pb::ExportFullViewingKeyResponse>, Status> {
161 Ok(Response::new(pb::ExportFullViewingKeyResponse {
162 full_viewing_key: Some(self.config.spend_key.full_viewing_key().clone().into()),
163 }))
164 }
165
166 async fn confirm_address(
167 &self,
168 request: Request<pb::ConfirmAddressRequest>,
169 ) -> Result<Response<pb::ConfirmAddressResponse>, Status> {
170 let address_index = request
171 .into_inner()
172 .address_index
173 .ok_or_else(|| {
174 Status::invalid_argument("missing address index in confirm address request")
175 })?
176 .try_into()
177 .map_err(|e| {
178 Status::invalid_argument(format!(
179 "invalid address index in confirm address request: {e:#}"
180 ))
181 })?;
182
183 let (address, _dtk) = self
184 .config
185 .spend_key
186 .full_viewing_key()
187 .payment_address(address_index);
188
189 Ok(Response::new(pb::ConfirmAddressResponse {
190 address: Some(address.into()),
191 }))
192 }
193}