1use dlp::{
2 args::{
3 MaybeEncryptedAccountMeta, MaybeEncryptedIxData, MaybeEncryptedPubkey,
4 PostDelegationActions,
5 },
6 compact,
7};
8use solana_sdk::{
9 instruction::{AccountMeta, Instruction},
10 signer::Signer,
11};
12use thiserror::Error;
13
14use crate::encryption::{self, EncryptionError, KEY_LEN};
15
16#[derive(Debug, Error)]
17pub enum DecryptError {
18 #[error(transparent)]
19 DecryptFailed(#[from] EncryptionError),
20
21 #[error("invalid decrypted pubkey length: {0}")]
22 InvalidPubkeyLength(usize),
23
24 #[error("invalid decrypted compact account meta length: {0}")]
25 InvalidAccountMetaLength(usize),
26
27 #[error("invalid decrypted compact account meta value: {0}")]
28 InvalidAccountMetaValue(u8),
29
30 #[error("invalid program_id index {index} for pubkey table len {len}")]
31 InvalidProgramIdIndex { index: u8, len: usize },
32
33 #[error("invalid account index {index} for pubkey table len {len}")]
34 InvalidAccountIndex { index: u8, len: usize },
35
36 #[error("invalid inserted signer count {inserted} for signers len {len}")]
37 InvalidInsertedSignerCount { inserted: u8, len: usize },
38
39 #[error("invalid inserted non-signer count {inserted} for non-signers len {len}")]
40 InvalidInsertedNonSignerCount { inserted: u8, len: usize },
41
42 #[error("non-signer (index {index}) cannot be used as signer (valid signer index ranges are {old_signer_range:?} and {new_signer_range:?}, start inclusive and end exclusive)")]
43 NonSignerCannotBeSigner {
44 index: usize,
45 old_signer_range: (usize, usize),
46 new_signer_range: (usize, usize),
47 },
48}
49
50pub trait Decrypt: Sized {
51 type Output;
52
53 fn decrypt(
54 self,
55 recipient_x25519_pubkey: &[u8; KEY_LEN],
56 recipient_x25519_secret: &[u8; KEY_LEN],
57 ) -> Result<Self::Output, DecryptError>;
58
59 fn decrypt_with_keypair(
60 self,
61 recipient_keypair: &solana_sdk::signature::Keypair,
62 ) -> Result<Self::Output, DecryptError>
63 where
64 Self: Sized,
65 {
66 let recipient_x25519_secret =
67 encryption::keypair_to_x25519_secret(recipient_keypair)?;
68 let recipient_x25519_pubkey = encryption::ed25519_pubkey_to_x25519(
69 recipient_keypair.pubkey().as_array(),
70 )?;
71 self.decrypt(&recipient_x25519_pubkey, &recipient_x25519_secret)
72 }
73}
74
75impl Decrypt for MaybeEncryptedPubkey {
76 type Output = [u8; 32];
77
78 fn decrypt(
79 self,
80 recipient_x25519_pubkey: &[u8; KEY_LEN],
81 recipient_x25519_secret: &[u8; KEY_LEN],
82 ) -> Result<Self::Output, DecryptError> {
83 match self {
84 Self::ClearText(pubkey) => Ok(pubkey),
85 Self::Encrypted(buffer) => {
86 let plaintext = encryption::decrypt(
87 buffer.as_bytes(),
88 recipient_x25519_pubkey,
89 recipient_x25519_secret,
90 )
91 .map_err(DecryptError::DecryptFailed)?;
92 Self::Output::try_from(plaintext.as_slice()).map_err(|_| {
93 DecryptError::InvalidPubkeyLength(plaintext.len())
94 })
95 }
96 }
97 }
98}
99
100impl Decrypt for MaybeEncryptedAccountMeta {
101 type Output = compact::AccountMeta;
102
103 fn decrypt(
104 self,
105 recipient_x25519_pubkey: &[u8; KEY_LEN],
106 recipient_x25519_secret: &[u8; KEY_LEN],
107 ) -> Result<Self::Output, DecryptError> {
108 match self {
109 Self::ClearText(account_meta) => Ok(account_meta),
110 Self::Encrypted(buffer) => {
111 let plaintext = encryption::decrypt(
112 buffer.as_bytes(),
113 recipient_x25519_pubkey,
114 recipient_x25519_secret,
115 )
116 .map_err(DecryptError::DecryptFailed)?;
117 if plaintext.len() != 1 {
118 return Err(DecryptError::InvalidAccountMetaLength(
119 plaintext.len(),
120 ));
121 }
122 compact::AccountMeta::from_byte(plaintext[0])
123 .ok_or(DecryptError::InvalidAccountMetaValue(plaintext[0]))
124 }
125 }
126 }
127}
128
129impl Decrypt for MaybeEncryptedIxData {
130 type Output = Vec<u8>;
131
132 fn decrypt(
133 self,
134 recipient_x25519_pubkey: &[u8; KEY_LEN],
135 recipient_x25519_secret: &[u8; KEY_LEN],
136 ) -> Result<Self::Output, DecryptError> {
137 let mut data = self.prefix;
138 if !self.suffix.as_bytes().is_empty() {
139 let suffix = encryption::decrypt(
140 self.suffix.as_bytes(),
141 recipient_x25519_pubkey,
142 recipient_x25519_secret,
143 )
144 .map_err(DecryptError::DecryptFailed)?;
145 data.extend_from_slice(&suffix);
146 }
147 Ok(data)
148 }
149}
150
151impl Decrypt for PostDelegationActions {
152 type Output = Vec<Instruction>;
153
154 fn decrypt(
157 self,
158 recipient_x25519_pubkey: &[u8; KEY_LEN],
159 recipient_x25519_secret: &[u8; KEY_LEN],
160 ) -> Result<Self::Output, DecryptError> {
161 let actions = self;
162 let inserted_signers = actions.inserted_signers as usize;
163 let inserted_non_signers = actions.inserted_non_signers as usize;
164 let signers_count = actions.signers.len();
165 let non_signers_count = actions.non_signers.len();
166
167 if inserted_signers > signers_count {
168 return Err(DecryptError::InvalidInsertedSignerCount {
169 inserted: actions.inserted_signers,
170 len: signers_count,
171 });
172 }
173 if inserted_non_signers > non_signers_count {
174 return Err(DecryptError::InvalidInsertedNonSignerCount {
175 inserted: actions.inserted_non_signers,
176 len: non_signers_count,
177 });
178 }
179
180 let pubkeys = {
184 let mut old_signers = actions.signers;
185 let new_signers = old_signers.split_off(inserted_signers);
186
187 let mut old_non_signers = actions
188 .non_signers
189 .iter()
190 .map(|non_signer| {
191 Ok(non_signer.clone().decrypt(
192 recipient_x25519_pubkey,
193 recipient_x25519_secret,
194 )?)
195 })
196 .collect::<Result<Vec<_>, DecryptError>>()?;
197
198 let new_non_signers =
199 old_non_signers.split_off(inserted_non_signers);
200
201 [old_signers, old_non_signers, new_signers, new_non_signers]
202 .concat()
203 };
204 let inserted_total = inserted_signers + inserted_non_signers;
205 let new_signers_count = signers_count - inserted_signers;
206
207 let old_signer_range = (0, inserted_signers);
208 let new_signer_range =
209 (inserted_total, inserted_total + new_signers_count);
210
211 let is_signer_idx = |idx: usize| {
212 (old_signer_range.0..old_signer_range.1).contains(&idx)
213 || (new_signer_range.0..new_signer_range.1).contains(&idx)
214 };
215
216 let instructions = actions
217 .instructions
218 .into_iter()
219 .map(|ix| {
220 Ok(Instruction {
221 program_id: pubkeys
222 .get(ix.program_id as usize)
223 .copied()
224 .ok_or(DecryptError::InvalidProgramIdIndex {
225 index: ix.program_id,
226 len: pubkeys.len(),
227 })?
228 .into(),
229
230 accounts: ix
231 .accounts
232 .into_iter()
233 .map(|maybe_compact_meta| {
234 let compact_meta = maybe_compact_meta.decrypt(
235 recipient_x25519_pubkey,
236 recipient_x25519_secret,
237 )?;
238 let idx = compact_meta.key() as usize;
239
240 if compact_meta.is_signer() && !is_signer_idx(idx) {
241 return Err(
242 DecryptError::NonSignerCannotBeSigner {
243 index: idx,
244 old_signer_range,
245 new_signer_range,
246 },
247 );
248 }
249
250 let account_pubkey = pubkeys
251 .get(idx)
252 .copied()
253 .ok_or(DecryptError::InvalidAccountIndex {
254 index: compact_meta.key(),
255 len: pubkeys.len(),
256 })?;
257 Ok(AccountMeta {
258 pubkey: account_pubkey.into(),
259 is_signer: compact_meta.is_signer(),
260 is_writable: compact_meta.is_writable(),
261 })
262 })
263 .collect::<Result<Vec<_>, DecryptError>>()?,
264
265 data: ix.data.decrypt(
266 recipient_x25519_pubkey,
267 recipient_x25519_secret,
268 )?,
269 })
270 })
271 .collect::<Result<Vec<_>, DecryptError>>()?;
272
273 Ok(instructions)
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use solana_program::instruction::AccountMeta;
280 use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer};
281
282 use super::*;
283 use crate::instruction_builder::{
284 Encrypt, Encryptable, EncryptableFrom, PostDelegationInstruction,
285 };
286
287 #[test]
288 fn test_post_delegation_actions_decrypt_roundtrip() {
289 let validator = Keypair::new();
290 let signer = Pubkey::new_unique();
291 let nonsigner = Pubkey::new_unique();
292 let program_id = Pubkey::new_unique();
293
294 let instructions = vec![PostDelegationInstruction {
295 program_id: program_id.cleartext(),
296 accounts: vec![
297 AccountMeta::new_readonly(signer, true).cleartext(),
298 AccountMeta::new_readonly(nonsigner, false).encrypted(),
299 ],
300 data: vec![1, 2, 3, 4].encrypted_from(2),
301 }];
302
303 let (actions, signers) = instructions
304 .encrypt(&validator.pubkey())
305 .expect("post-delegation actions encryption failed");
306
307 assert_eq!(signers, vec![AccountMeta::new_readonly(signer, true)]);
308
309 let decrypted = actions.decrypt_with_keypair(&validator).unwrap();
310
311 assert_eq!(
312 decrypted,
313 vec![Instruction {
314 program_id,
315 accounts: vec![
316 AccountMeta::new_readonly(signer, true),
317 AccountMeta::new_readonly(nonsigner, false)
318 ],
319 data: vec![1, 2, 3, 4]
320 }]
321 );
322 }
323}