thru_base/
crypto_utils.rs

1//! Cryptographic utilities for the Thru ecosystem
2
3use crate::tn_tools::Pubkey;
4use sha2::{Digest, Sha256};
5
6const TN_UPLOADER_PROGRAM_SEED_SIZE: usize = 32;
7
8/// Derives both meta and buffer account pubkeys for an uploader program
9///
10/// # Arguments
11/// * `seed` - The seed bytes for account derivation (must not be empty)
12/// * `program_id` - The program ID pubkey
13///
14/// # Returns
15/// A Result containing a tuple of (meta_account_pubkey, buffer_account_pubkey)
16///
17/// # Errors
18/// Returns an error if the seed is empty
19pub fn derive_uploader_program_accounts(
20    seed: &[u8],
21    program_id: &Pubkey,
22) -> Result<(Pubkey, Pubkey), Box<dyn std::error::Error>> {
23    if seed.is_empty() {
24        return Err("Seed cannot be empty".into());
25    }
26    if seed.len() > TN_UPLOADER_PROGRAM_SEED_SIZE {
27        return Err("Seed cannot be greater than 32 bytes".into());
28    }
29    // pad seed to 32 bytes with zeros
30    let mut padded_seed = [0u8; TN_UPLOADER_PROGRAM_SEED_SIZE];
31    padded_seed[..seed.len()].copy_from_slice(seed);
32
33    // Meta account derivation
34    let meta_account = derive_program_address(&padded_seed, program_id, true)?;
35
36    // Buffer account derivation
37    let meta_bytes = meta_account.to_bytes()?;
38    let buffer_account = derive_program_address(&meta_bytes, program_id, true)?;
39
40    Ok((meta_account, buffer_account))
41}
42
43/// Derives both meta and program account pubkeys for a manager program
44///
45/// # Arguments
46/// * `seed` - The seed bytes for account derivation (must not be empty)
47/// * `program_id` - The program ID pubkey
48/// * `is_ephemeral` - Whether the program is ephemeral
49///
50/// # Returns
51/// A Result containing a tuple of (meta_account_pubkey, program_account_pubkey)
52///
53/// # Errors
54/// Returns an error if the seed is empty
55pub fn derive_manager_program_accounts(
56    seed: &[u8],
57    program_id: &Pubkey,
58    is_ephemeral: bool,
59) -> Result<(Pubkey, Pubkey), Box<dyn std::error::Error>> {
60    if seed.is_empty() {
61        return Err("Seed cannot be empty".into());
62    }
63    if seed.len() > TN_UPLOADER_PROGRAM_SEED_SIZE {
64        return Err("Seed cannot be greater than 32 bytes".into());
65    }
66    // pad seed to 32 bytes with zeros
67    let mut padded_seed = [0u8; TN_UPLOADER_PROGRAM_SEED_SIZE];
68    padded_seed[..seed.len()].copy_from_slice(seed);
69
70    // Meta account derivation
71    let meta_account = derive_program_address(&padded_seed, program_id, is_ephemeral)?;
72
73    // Program account derivation - use meta account's bytes as seed for program account
74    let meta_bytes = meta_account.to_bytes()?;
75    let program_account = derive_program_address(&meta_bytes, program_id, is_ephemeral)?;
76
77    Ok((meta_account, program_account))
78}
79
80/// Derives a program address from seed components and program ID
81///
82/// This function creates a deterministic address by hashing the program ID,
83/// an ephemeral account flag, and the provided seed components.
84///
85/// # Arguments
86/// * `seeds` - Array of byte slices to use as seed components
87/// * `program_id` - The program ID pubkey
88///
89/// # Returns
90/// A Result containing a tuple of (derived_pubkey, bump_seed)
91/// The bump_seed is always 0 in this implementation as we don't use bump seed derivation
92///
93/// # Errors
94/// Returns an error if the program_id cannot be converted to bytes
95pub fn derive_program_address(
96    seed: &[u8; 32],
97    program_id: &Pubkey,
98    is_ephemeral: bool,
99) -> Result<Pubkey, Box<dyn std::error::Error>> {
100    // Get program ID bytes
101    let program_bytes = program_id
102        .to_bytes()
103        .map_err(|e| format!("Failed to convert program_id to bytes: {}", e))?;
104
105    // Create derivation by hashing program_id + ephemeral flag + seeds
106    let mut hasher = Sha256::new();
107    hasher.update(&program_bytes);
108
109    // Add ephemeral account flag
110    hasher.update(&[if is_ephemeral { 1u8 } else { 0u8 }]); // ephemeral account flag
111
112    // Add seed
113    hasher.update(seed);
114
115    let hash = hasher.finalize();
116
117    // Use first 32 bytes as the derived address
118    let mut derived_bytes = [0u8; 32];
119    derived_bytes.copy_from_slice(&hash[..32]);
120
121    Ok(Pubkey::from_bytes(&derived_bytes))
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_derive_uploader_program_accounts() {
130        // Create test program ID
131        let program_bytes = [1u8; 32];
132        let program_id = Pubkey::from_bytes(&program_bytes);
133
134        // Test with valid seed
135        let seed = b"test_seed";
136        let result = derive_uploader_program_accounts(seed, &program_id);
137        assert!(result.is_ok());
138
139        let (meta_account, buffer_account) = result.unwrap();
140        assert_ne!(meta_account, buffer_account);
141
142        // Test deterministic derivation - same inputs should produce same outputs
143        let result2 = derive_uploader_program_accounts(seed, &program_id);
144        let (meta_account2, buffer_account2) = result2.unwrap();
145        assert_eq!(meta_account, meta_account2);
146        assert_eq!(buffer_account, buffer_account2);
147    }
148
149    #[test]
150    fn test_derive_uploader_program_accounts_empty_seed() {
151        let program_bytes = [1u8; 32];
152        let program_id = Pubkey::from_bytes(&program_bytes);
153
154        // Test with empty seed should return error
155        let empty_seed = b"";
156        let result = derive_uploader_program_accounts(empty_seed, &program_id);
157        assert!(result.is_err());
158        assert_eq!(result.unwrap_err().to_string(), "Seed cannot be empty");
159    }
160
161    #[test]
162    fn test_derive_program_address_different_seeds() {
163        let program_bytes = [1u8; 32];
164        let program_id = Pubkey::from_bytes(&program_bytes);
165
166        let mut seed1 = [0u8; 32];
167        seed1[..4].copy_from_slice(b"test");
168        let mut seed2 = [0u8; 32];
169        seed2[..4].copy_from_slice(b"seed");
170
171        let pubkey1 = derive_program_address(&seed1, &program_id, false).unwrap();
172        let pubkey2 = derive_program_address(&seed2, &program_id, false).unwrap();
173
174        // Different seeds should produce different addresses
175        assert_ne!(pubkey1, pubkey2);
176    }
177}