Skip to main content

dlp_api/
decrypt.rs

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    /// This function decrypts PostDelegationActions as well as
155    /// validates it, matching the expected signers with the AccountMetas.
156    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        // Rebuild the lookup table in the same order used during compact
181        // index assignment (see compact module):
182        // [old signers][old non-signers][new signers][new non-signers]
183        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}