spl_tlv_account_resolution/
seeds.rs

1//! Types for managing seed configurations in TLV Account Resolution
2//!
3//! As determined by the `address_config` field of `ExtraAccountMeta`,
4//! seed configurations are limited to a maximum of 32 bytes.
5//! This means that the maximum number of seed configurations that can be
6//! packed into a single `ExtraAccountMeta` will depend directly on the size
7//! of the seed configurations themselves.
8//!
9//! Sizes are as follows:
10//!     * `Seed::Literal`: `1 + 1 + N`
11//!         * 1 - Discriminator
12//!         * 1 - Length of literal
13//!         * N - Literal bytes themselves
14//!     * `Seed::InstructionData`: `1 + 1 + 1 = 3`
15//!         * 1 - Discriminator
16//!         * 1 - Start index of instruction data
17//!         * 1 - Length of instruction data starting at index
18//!     * `Seed::AccountKey`: `1 + 1 = 2`
19//!         * 1 - Discriminator
20//!         * 1 - Index of account in accounts list
21//!     * `Seed::AccountData`: `1 + 1 + 1 + 1 = 4`
22//!         * 1 - Discriminator
23//!         * 1 - Index of account in accounts list
24//!         * 1 - Start index of account data
25//!         * 1 - Length of account data starting at index
26//!
27//! No matter which types of seeds you choose, the total size of all seed
28//! configurations must be less than or equal to 32 bytes.
29
30#[cfg(feature = "serde-traits")]
31use serde::{Deserialize, Serialize};
32use {crate::error::AccountResolutionError, solana_program_error::ProgramError};
33
34/// Enum to describe a required seed for a Program-Derived Address
35#[derive(Clone, Debug, PartialEq)]
36#[cfg_attr(feature = "serde-traits", derive(Serialize, Deserialize))]
37#[cfg_attr(feature = "serde-traits", serde(rename_all = "camelCase"))]
38pub enum Seed {
39    /// Uninitialized configuration byte space
40    Uninitialized,
41    /// A literal hard-coded argument
42    /// Packed as:
43    ///     * 1 - Discriminator
44    ///     * 1 - Length of literal
45    ///     * N - Literal bytes themselves
46    Literal {
47        /// The literal value represented as a vector of bytes.
48        ///
49        /// For example, if a literal value is a string literal,
50        /// such as "my-seed", this value would be
51        /// `"my-seed".as_bytes().to_vec()`.
52        bytes: Vec<u8>,
53    },
54    /// An instruction-provided argument, to be resolved from the instruction
55    /// data
56    /// Packed as:
57    ///     * 1 - Discriminator
58    ///     * 1 - Start index of instruction data
59    ///     * 1 - Length of instruction data starting at index
60    InstructionData {
61        /// The index where the bytes of an instruction argument begin
62        index: u8,
63        /// The length of the instruction argument (number of bytes)
64        ///
65        /// Note: Max seed length is 32 bytes, so `u8` is appropriate here
66        length: u8,
67    },
68    /// The public key of an account from the entire accounts list.
69    /// Note: This includes an extra accounts required.
70    ///
71    /// Packed as:
72    ///     * 1 - Discriminator
73    ///     * 1 - Index of account in accounts list
74    AccountKey {
75        /// The index of the account in the entire accounts list
76        index: u8,
77    },
78    /// An argument to be resolved from the inner data of some account
79    /// Packed as:
80    ///     * 1 - Discriminator
81    ///     * 1 - Index of account in accounts list
82    ///     * 1 - Start index of account data
83    ///     * 1 - Length of account data starting at index
84    #[cfg_attr(
85        feature = "serde-traits",
86        serde(rename_all = "camelCase", alias = "account_data")
87    )]
88    AccountData {
89        /// The index of the account in the entire accounts list
90        account_index: u8,
91        /// The index where the bytes of an account data argument begin
92        data_index: u8,
93        /// The length of the argument (number of bytes)
94        ///
95        /// Note: Max seed length is 32 bytes, so `u8` is appropriate here
96        length: u8,
97    },
98}
99impl Seed {
100    /// Get the size of a seed configuration
101    pub fn tlv_size(&self) -> u8 {
102        match &self {
103            // 1 byte for the discriminator
104            Self::Uninitialized => 0,
105            // 1 byte for the discriminator, 1 byte for the length of the bytes, then the raw bytes
106            Self::Literal { bytes } => 1 + 1 + bytes.len() as u8,
107            // 1 byte for the discriminator, 1 byte for the index, 1 byte for the length
108            Self::InstructionData { .. } => 1 + 1 + 1,
109            // 1 byte for the discriminator, 1 byte for the index
110            Self::AccountKey { .. } => 1 + 1,
111            // 1 byte for the discriminator, 1 byte for the account index,
112            // 1 byte for the data index 1 byte for the length
113            Self::AccountData { .. } => 1 + 1 + 1 + 1,
114        }
115    }
116
117    /// Packs a seed configuration into a slice
118    pub fn pack(&self, dst: &mut [u8]) -> Result<(), ProgramError> {
119        if dst.len() != self.tlv_size() as usize {
120            return Err(AccountResolutionError::NotEnoughBytesForSeed.into());
121        }
122        if dst.len() > 32 {
123            return Err(AccountResolutionError::SeedConfigsTooLarge.into());
124        }
125        match &self {
126            Self::Uninitialized => return Err(AccountResolutionError::InvalidSeedConfig.into()),
127            Self::Literal { bytes } => {
128                dst[0] = 1;
129                dst[1] = bytes.len() as u8;
130                dst[2..].copy_from_slice(bytes);
131            }
132            Self::InstructionData { index, length } => {
133                dst[0] = 2;
134                dst[1] = *index;
135                dst[2] = *length;
136            }
137            Self::AccountKey { index } => {
138                dst[0] = 3;
139                dst[1] = *index;
140            }
141            Self::AccountData {
142                account_index,
143                data_index,
144                length,
145            } => {
146                dst[0] = 4;
147                dst[1] = *account_index;
148                dst[2] = *data_index;
149                dst[3] = *length;
150            }
151        }
152        Ok(())
153    }
154
155    /// Packs a vector of seed configurations into a 32-byte array,
156    /// filling the rest with zeroes. Errors if it overflows.
157    pub fn pack_into_address_config(seeds: &[Self]) -> Result<[u8; 32], ProgramError> {
158        let mut packed = [0u8; 32];
159        let mut i: usize = 0;
160        for seed in seeds {
161            let seed_size = seed.tlv_size() as usize;
162            let slice_end = i + seed_size;
163            if slice_end > 32 {
164                return Err(AccountResolutionError::SeedConfigsTooLarge.into());
165            }
166            seed.pack(&mut packed[i..slice_end])?;
167            i = slice_end;
168        }
169        Ok(packed)
170    }
171
172    /// Unpacks a seed configuration from a slice
173    pub fn unpack(bytes: &[u8]) -> Result<Self, ProgramError> {
174        let (discrim, rest) = bytes
175            .split_first()
176            .ok_or::<ProgramError>(ProgramError::InvalidAccountData)?;
177        match discrim {
178            0 => Ok(Self::Uninitialized),
179            1 => unpack_seed_literal(rest),
180            2 => unpack_seed_instruction_arg(rest),
181            3 => unpack_seed_account_key(rest),
182            4 => unpack_seed_account_data(rest),
183            _ => Err(ProgramError::InvalidAccountData),
184        }
185    }
186
187    /// Unpacks all seed configurations from a 32-byte array.
188    /// Stops when it hits uninitialized data (zeroes).
189    pub fn unpack_address_config(address_config: &[u8; 32]) -> Result<Vec<Self>, ProgramError> {
190        let mut seeds = vec![];
191        let mut i = 0;
192        while i < 32 {
193            let seed = Self::unpack(&address_config[i..])?;
194            let seed_size = seed.tlv_size() as usize;
195            i += seed_size;
196            if seed == Self::Uninitialized {
197                break;
198            }
199            seeds.push(seed);
200        }
201        Ok(seeds)
202    }
203}
204
205fn unpack_seed_literal(bytes: &[u8]) -> Result<Seed, ProgramError> {
206    let (length, rest) = bytes
207        .split_first()
208        // Should be at least 1 byte
209        .ok_or::<ProgramError>(AccountResolutionError::InvalidBytesForSeed.into())?;
210    let length = *length as usize;
211    if rest.len() < length {
212        // Should be at least `length` bytes
213        return Err(AccountResolutionError::InvalidBytesForSeed.into());
214    }
215    Ok(Seed::Literal {
216        bytes: rest[..length].to_vec(),
217    })
218}
219
220fn unpack_seed_instruction_arg(bytes: &[u8]) -> Result<Seed, ProgramError> {
221    if bytes.len() < 2 {
222        // Should be at least 2 bytes
223        return Err(AccountResolutionError::InvalidBytesForSeed.into());
224    }
225    Ok(Seed::InstructionData {
226        index: bytes[0],
227        length: bytes[1],
228    })
229}
230
231fn unpack_seed_account_key(bytes: &[u8]) -> Result<Seed, ProgramError> {
232    if bytes.is_empty() {
233        // Should be at least 1 byte
234        return Err(AccountResolutionError::InvalidBytesForSeed.into());
235    }
236    Ok(Seed::AccountKey { index: bytes[0] })
237}
238
239fn unpack_seed_account_data(bytes: &[u8]) -> Result<Seed, ProgramError> {
240    if bytes.len() < 3 {
241        // Should be at least 3 bytes
242        return Err(AccountResolutionError::InvalidBytesForSeed.into());
243    }
244    Ok(Seed::AccountData {
245        account_index: bytes[0],
246        data_index: bytes[1],
247        length: bytes[2],
248    })
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_pack() {
257        // Seed too large
258        let seed = Seed::Literal { bytes: vec![1; 33] };
259        let mut packed = vec![0u8; seed.tlv_size() as usize];
260        assert_eq!(
261            seed.pack(&mut packed).unwrap_err(),
262            AccountResolutionError::SeedConfigsTooLarge.into()
263        );
264        assert_eq!(
265            Seed::pack_into_address_config(&[seed]).unwrap_err(),
266            AccountResolutionError::SeedConfigsTooLarge.into()
267        );
268
269        // Should fail if the length is wrong
270        let seed = Seed::Literal { bytes: vec![1; 12] };
271        let mut packed = vec![0u8; seed.tlv_size() as usize - 1];
272        assert_eq!(
273            seed.pack(&mut packed).unwrap_err(),
274            AccountResolutionError::NotEnoughBytesForSeed.into()
275        );
276
277        // Can't pack a `Seed::Uninitialized`
278        let seed = Seed::Uninitialized;
279        let mut packed = vec![0u8; seed.tlv_size() as usize];
280        assert_eq!(
281            seed.pack(&mut packed).unwrap_err(),
282            AccountResolutionError::InvalidSeedConfig.into()
283        );
284    }
285
286    #[test]
287    fn test_pack_address_config() {
288        // Should fail if one seed is too large
289        let seed = Seed::Literal { bytes: vec![1; 36] };
290        assert_eq!(
291            Seed::pack_into_address_config(&[seed]).unwrap_err(),
292            AccountResolutionError::SeedConfigsTooLarge.into()
293        );
294
295        // Should fail if the combination of all seeds is too large
296        let seed1 = Seed::Literal { bytes: vec![1; 30] }; // 30 bytes
297        let seed2 = Seed::InstructionData {
298            index: 0,
299            length: 4,
300        }; // 3 bytes
301        assert_eq!(
302            Seed::pack_into_address_config(&[seed1, seed2]).unwrap_err(),
303            AccountResolutionError::SeedConfigsTooLarge.into()
304        );
305    }
306
307    #[test]
308    fn test_unpack() {
309        // Can unpack zeroes
310        let zeroes = [0u8; 32];
311        let seeds = Seed::unpack_address_config(&zeroes).unwrap();
312        assert_eq!(seeds, vec![]);
313
314        // Should fail for empty bytes
315        let bytes = [];
316        assert_eq!(
317            Seed::unpack(&bytes).unwrap_err(),
318            ProgramError::InvalidAccountData
319        );
320
321        // Should fail if bytes are malformed for literal seed
322        let bytes = [
323            1, // Discrim (Literal)
324            4, // Length
325            1, 1, 1, // Incorrect length
326        ];
327        assert_eq!(
328            Seed::unpack(&bytes).unwrap_err(),
329            AccountResolutionError::InvalidBytesForSeed.into()
330        );
331
332        // Should fail if bytes are malformed for literal seed
333        let bytes = [
334            2, // Discrim (InstructionData)
335            2, // Index (Length missing)
336        ];
337        assert_eq!(
338            Seed::unpack(&bytes).unwrap_err(),
339            AccountResolutionError::InvalidBytesForSeed.into()
340        );
341
342        // Should fail if bytes are malformed for literal seed
343        let bytes = [
344            3, // Discrim (AccountKey, Index missing)
345        ];
346        assert_eq!(
347            Seed::unpack(&bytes).unwrap_err(),
348            AccountResolutionError::InvalidBytesForSeed.into()
349        );
350    }
351
352    #[test]
353    fn test_unpack_address_config() {
354        // Should fail if bytes are malformed
355        let bytes = [
356            1, // Discrim (Literal)
357            4, // Length
358            1, 1, 1, 1, // 4
359            6, // Discrim (Invalid)
360            2, // Index
361            1, // Length
362            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363        ];
364        assert_eq!(
365            Seed::unpack_address_config(&bytes).unwrap_err(),
366            ProgramError::InvalidAccountData
367        );
368
369        // Should fail if 32nd byte is not zero, but it would be the
370        // start of a config
371        //
372        // Namely, if a seed config is unpacked and leaves 1 byte remaining,
373        // it has to be 0, since no valid seed config can be 1 byte long
374        let bytes = [
375            1,  // Discrim (Literal)
376            16, // Length
377            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 16
378            1,  // Discrim (Literal)
379            11, // Length
380            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 11
381            2, // Non-zero byte
382        ];
383        assert_eq!(
384            Seed::unpack_address_config(&bytes).unwrap_err(),
385            AccountResolutionError::InvalidBytesForSeed.into(),
386        );
387
388        // Should pass if 31st byte is not zero, but it would be
389        // the start of a config
390        //
391        // Similar to above, however we now have 2 bytes to work with,
392        // which could be a valid seed config
393        let bytes = [
394            1,  // Discrim (Literal)
395            16, // Length
396            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 16
397            1,  // Discrim (Literal)
398            10, // Length
399            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10
400            3, // Non-zero byte - Discrim (AccountKey)
401            0, // Index
402        ];
403        assert_eq!(
404            Seed::unpack_address_config(&bytes).unwrap(),
405            vec![
406                Seed::Literal {
407                    bytes: vec![1u8; 16]
408                },
409                Seed::Literal {
410                    bytes: vec![1u8; 10]
411                },
412                Seed::AccountKey { index: 0 }
413            ],
414        );
415
416        // Should fail if 31st byte is not zero and a valid seed config
417        // discriminator, but the seed config requires more than 2 bytes
418        let bytes = [
419            1,  // Discrim (Literal)
420            16, // Length
421            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  // 16
422            1,  // Discrim (Literal)
423            10, // Length
424            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10
425            2, // Non-zero byte - Discrim (InstructionData)
426            0, // Index (Length missing)
427        ];
428        assert_eq!(
429            Seed::unpack_address_config(&bytes).unwrap_err(),
430            AccountResolutionError::InvalidBytesForSeed.into(),
431        );
432    }
433
434    fn test_pack_unpack_seed(seed: Seed) {
435        let tlv_size = seed.tlv_size() as usize;
436        let mut packed = vec![0u8; tlv_size];
437        seed.pack(&mut packed).unwrap();
438        let unpacked = Seed::unpack(&packed).unwrap();
439        assert_eq!(seed, unpacked);
440    }
441
442    #[test]
443    fn test_pack_unpack() {
444        let mut mixed = vec![];
445
446        // Literals
447
448        let bytes = b"hello";
449        let seed = Seed::Literal {
450            bytes: bytes.to_vec(),
451        };
452        test_pack_unpack_seed(seed);
453
454        let bytes = 8u8.to_le_bytes();
455        let seed = Seed::Literal {
456            bytes: bytes.to_vec(),
457        };
458        test_pack_unpack_seed(seed.clone());
459        mixed.push(seed);
460
461        let bytes = 32u32.to_le_bytes();
462        let seed = Seed::Literal {
463            bytes: bytes.to_vec(),
464        };
465        test_pack_unpack_seed(seed.clone());
466        mixed.push(seed);
467
468        // Instruction args
469
470        let seed = Seed::InstructionData {
471            index: 0,
472            length: 0,
473        };
474        test_pack_unpack_seed(seed);
475
476        let seed = Seed::InstructionData {
477            index: 6,
478            length: 9,
479        };
480        test_pack_unpack_seed(seed.clone());
481        mixed.push(seed);
482
483        // Account keys
484
485        let seed = Seed::AccountKey { index: 0 };
486        test_pack_unpack_seed(seed);
487
488        let seed = Seed::AccountKey { index: 9 };
489        test_pack_unpack_seed(seed.clone());
490        mixed.push(seed);
491
492        // Account data
493
494        let seed = Seed::AccountData {
495            account_index: 0,
496            data_index: 0,
497            length: 0,
498        };
499        test_pack_unpack_seed(seed);
500
501        let seed = Seed::AccountData {
502            account_index: 0,
503            data_index: 0,
504            length: 9,
505        };
506        test_pack_unpack_seed(seed.clone());
507        mixed.push(seed);
508
509        // Arrays
510
511        let packed_array = Seed::pack_into_address_config(&mixed).unwrap();
512        let unpacked_array = Seed::unpack_address_config(&packed_array).unwrap();
513        assert_eq!(mixed, unpacked_array);
514
515        let mut shuffled_mixed = mixed.clone();
516        shuffled_mixed.swap(0, 1);
517        shuffled_mixed.swap(1, 4);
518        shuffled_mixed.swap(3, 0);
519
520        let packed_array = Seed::pack_into_address_config(&shuffled_mixed).unwrap();
521        let unpacked_array = Seed::unpack_address_config(&packed_array).unwrap();
522        assert_eq!(shuffled_mixed, unpacked_array);
523    }
524}