cbe_program/
pubkey.rs

1//! Cartallum CBE account addresses.
2
3#![allow(clippy::integer_arithmetic)]
4use {
5    crate::{decode_error::DecodeError, hash::hashv, wasm_bindgen},
6    borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
7    bytemuck::{Pod, Zeroable},
8    num_derive::{FromPrimitive, ToPrimitive},
9    std::{
10        convert::{Infallible, TryFrom},
11        fmt, mem,
12        str::FromStr,
13    },
14    thiserror::Error,
15};
16
17/// Number of bytes in a pubkey
18pub const PUBKEY_BYTES: usize = 32;
19/// maximum length of derived `Pubkey` seed
20pub const MAX_SEED_LEN: usize = 32;
21/// Maximum number of seeds
22pub const MAX_SEEDS: usize = 16;
23/// Maximum string length of a base58 encoded pubkey
24const MAX_BASE58_LEN: usize = 44;
25
26const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
27
28#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
29pub enum PubkeyError {
30    /// Length of the seed is too long for address generation
31    #[error("Length of the seed is too long for address generation")]
32    MaxSeedLengthExceeded,
33    #[error("Provided seeds do not result in a valid address")]
34    InvalidSeeds,
35    #[error("Provided owner is not allowed")]
36    IllegalOwner,
37}
38impl<T> DecodeError<T> for PubkeyError {
39    fn type_of() -> &'static str {
40        "PubkeyError"
41    }
42}
43impl From<u64> for PubkeyError {
44    fn from(error: u64) -> Self {
45        match error {
46            0 => PubkeyError::MaxSeedLengthExceeded,
47            1 => PubkeyError::InvalidSeeds,
48            _ => panic!("Unsupported PubkeyError"),
49        }
50    }
51}
52
53/// The address of a [Cartallum CBE account][acc].
54///
55/// Some account addresses are [ed25519] public keys, with corresponding secret
56/// keys that are managed off-chain. Often, though, account addresses do not
57/// have corresponding secret keys &mdash; as with [_program derived
58/// addresses_][pdas] &mdash; or the secret key is not relevant to the operation
59/// of a program, and may have even been disposed of. As running Cartallum CBE programs
60/// can not safely create or manage secret keys, the full [`Keypair`] is not
61/// defined in `cbe-program` but in `cbe-sdk`.
62///
63/// [acc]: https://docs.cartallum.com/developing/programming-model/accounts
64/// [ed25519]: https://ed25519.cr.yp.to/
65/// [pdas]: https://docs.cartallum.com/developing/programming-model/calling-between-programs#program-derived-addresses
66/// [`Keypair`]: https://docs.cartallum.com/cbe-sdk/latest/cbe_sdk/signer/keypair/struct.Keypair.html
67#[wasm_bindgen]
68#[repr(transparent)]
69#[derive(
70    AbiExample,
71    BorshDeserialize,
72    BorshSchema,
73    BorshSerialize,
74    Clone,
75    Copy,
76    Default,
77    Deserialize,
78    Eq,
79    Hash,
80    Ord,
81    PartialEq,
82    PartialOrd,
83    Pod,
84    Serialize,
85    Zeroable,
86)]
87pub struct Pubkey(pub(crate) [u8; 32]);
88
89impl crate::sanitize::Sanitize for Pubkey {}
90
91#[derive(Error, Debug, Serialize, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
92pub enum ParsePubkeyError {
93    #[error("String is the wrong size")]
94    WrongSize,
95    #[error("Invalid Base58 string")]
96    Invalid,
97}
98
99impl From<Infallible> for ParsePubkeyError {
100    fn from(_: Infallible) -> Self {
101        unreachable!("Infallible uninhabited");
102    }
103}
104
105impl<T> DecodeError<T> for ParsePubkeyError {
106    fn type_of() -> &'static str {
107        "ParsePubkeyError"
108    }
109}
110
111impl FromStr for Pubkey {
112    type Err = ParsePubkeyError;
113
114    fn from_str(s: &str) -> Result<Self, Self::Err> {
115        if s.len() > MAX_BASE58_LEN {
116            return Err(ParsePubkeyError::WrongSize);
117        }
118        let pubkey_vec = bs58::decode(s)
119            .into_vec()
120            .map_err(|_| ParsePubkeyError::Invalid)?;
121        if pubkey_vec.len() != mem::size_of::<Pubkey>() {
122            Err(ParsePubkeyError::WrongSize)
123        } else {
124            Ok(Pubkey::new(&pubkey_vec))
125        }
126    }
127}
128
129impl From<[u8; 32]> for Pubkey {
130    fn from(from: [u8; 32]) -> Self {
131        Self(from)
132    }
133}
134
135impl TryFrom<&str> for Pubkey {
136    type Error = ParsePubkeyError;
137    fn try_from(s: &str) -> Result<Self, Self::Error> {
138        Pubkey::from_str(s)
139    }
140}
141
142pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
143    #[cfg(not(target_os = "cbe"))]
144    {
145        curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
146            .decompress()
147            .is_some()
148    }
149    #[cfg(target_os = "cbe")]
150    unimplemented!();
151}
152
153impl Pubkey {
154    pub fn new(pubkey_vec: &[u8]) -> Self {
155        Self(
156            <[u8; 32]>::try_from(<&[u8]>::clone(&pubkey_vec))
157                .expect("Slice must be the same length as a Pubkey"),
158        )
159    }
160
161    pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
162        Self(pubkey_array)
163    }
164
165    #[deprecated(since = "1.3.9", note = "Please use 'Pubkey::new_unique' instead")]
166    #[cfg(not(target_os = "cbe"))]
167    pub fn new_rand() -> Self {
168        // Consider removing Pubkey::new_rand() entirely in the v1.5 or v1.6 timeframe
169        Pubkey::new(&rand::random::<[u8; 32]>())
170    }
171
172    /// unique Pubkey for tests and benchmarks.
173    pub fn new_unique() -> Self {
174        use crate::atomic_u64::AtomicU64;
175        static I: AtomicU64 = AtomicU64::new(1);
176
177        let mut b = [0u8; 32];
178        let i = I.fetch_add(1);
179        // use big endian representation to ensure that recent unique pubkeys
180        // are always greater than less recent unique pubkeys
181        b[0..8].copy_from_slice(&i.to_be_bytes());
182        Self::new(&b)
183    }
184
185    pub fn create_with_seed(
186        base: &Pubkey,
187        seed: &str,
188        owner: &Pubkey,
189    ) -> Result<Pubkey, PubkeyError> {
190        if seed.len() > MAX_SEED_LEN {
191            return Err(PubkeyError::MaxSeedLengthExceeded);
192        }
193
194        let owner = owner.as_ref();
195        if owner.len() >= PDA_MARKER.len() {
196            let slice = &owner[owner.len() - PDA_MARKER.len()..];
197            if slice == PDA_MARKER {
198                return Err(PubkeyError::IllegalOwner);
199            }
200        }
201
202        Ok(Pubkey::new(
203            hashv(&[base.as_ref(), seed.as_ref(), owner]).as_ref(),
204        ))
205    }
206
207    /// Find a valid [program derived address][pda] and its corresponding bump seed.
208    ///
209    /// [pda]: https://docs.cartallum.com/developing/programming-model/calling-between-programs#program-derived-addresses
210    ///
211    /// Program derived addresses (PDAs) are account keys that only the program,
212    /// `program_id`, has the authority to sign. The address is of the same form
213    /// as a Cartallum CBE `Pubkey`, except they are ensured to not be on the ed25519
214    /// curve and thus have no associated private key. When performing
215    /// cross-program invocations the program can "sign" for the key by calling
216    /// [`invoke_signed`] and passing the same seeds used to generate the
217    /// address, along with the calculated _bump seed_, which this function
218    /// returns as the second tuple element. The runtime will verify that the
219    /// program associated with this address is the caller and thus authorized
220    /// to be the signer.
221    ///
222    /// [`invoke_signed`]: crate::program::invoke_signed
223    ///
224    /// The `seeds` are application-specific, and must be carefully selected to
225    /// uniquely derive accounts per application requirements. It is common to
226    /// use static strings and other pubkeys as seeds.
227    ///
228    /// Because the program address must not lie on the ed25519 curve, there may
229    /// be seed and program id combinations that are invalid. For this reason,
230    /// an extra seed (the bump seed) is calculated that results in a
231    /// point off the curve. The bump seed must be passed as an additional seed
232    /// when calling `invoke_signed`.
233    ///
234    /// The processes of finding a valid program address is by trial and error,
235    /// and even though it is deterministic given a set of inputs it can take a
236    /// variable amount of time to succeed across different inputs.  This means
237    /// that when called from an on-chain program it may incur a variable amount
238    /// of the program's compute budget.  Programs that are meant to be very
239    /// performant may not want to use this function because it could take a
240    /// considerable amount of time. Programs that are already at risk
241    /// of exceeding their compute budget should call this with care since
242    /// there is a chance that the program's budget may be occasionally
243    /// and unpredictably exceeded.
244    ///
245    /// As all account addresses accessed by an on-chain Cartallum CBE program must be
246    /// explicitly passed to the program, it is typical for the PDAs to be
247    /// derived in off-chain client programs, avoiding the compute cost of
248    /// generating the address on-chain. The address may or may not then be
249    /// verified by re-deriving it on-chain, depending on the requirements of
250    /// the program. This verification may be performed without the overhead of
251    /// re-searching for the bump key by using the [`create_program_address`]
252    /// function.
253    ///
254    /// [`create_program_address`]: Pubkey::create_program_address
255    ///
256    /// **Warning**: Because of the way the seeds are hashed there is a potential
257    /// for program address collisions for the same program id.  The seeds are
258    /// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
259    /// and {"ab", "cd", "ef"} will all result in the same program address given
260    /// the same program id. Since the chance of collision is local to a given
261    /// program id, the developer of that program must take care to choose seeds
262    /// that do not collide with each other. For seed schemes that are susceptible
263    /// to this type of hash collision, a common remedy is to insert separators
264    /// between seeds, e.g. transforming {"abc", "def"} into {"abc", "-", "def"}.
265    ///
266    /// # Panics
267    ///
268    /// Panics in the statistically improbable event that a bump seed could not be
269    /// found. Use [`try_find_program_address`] to handle this case.
270    ///
271    /// [`try_find_program_address`]: Pubkey::try_find_program_address
272    ///
273    /// Panics if any of the following are true:
274    ///
275    /// - the number of provided seeds is greater than, _or equal to_,  [`MAX_SEEDS`],
276    /// - any individual seed's length is greater than [`MAX_SEED_LEN`].
277    ///
278    /// # Examples
279    ///
280    /// This example illustrates a simple case of creating a "vault" account
281    /// which is derived from the payer account, but owned by an on-chain
282    /// program. The program derived address is derived in an off-chain client
283    /// program, which invokes an on-chain Cartallum CBE program that uses the address
284    /// to create a new account owned and controlled by the program itself.
285    ///
286    /// By convention, the on-chain program will be compiled for use in two
287    /// different contexts: both on-chain, to interpret a custom program
288    /// instruction as a Cartallum CBE transaction; and off-chain, as a library, so
289    /// that clients can share the instruction data structure, constructors, and
290    /// other common code.
291    ///
292    /// First the on-chain Cartallum CBE program:
293    ///
294    /// ```
295    /// # use borsh::{BorshSerialize, BorshDeserialize};
296    /// # use cbe_program::{
297    /// #     pubkey::Pubkey,
298    /// #     entrypoint::ProgramResult,
299    /// #     program::invoke_signed,
300    /// #     system_instruction,
301    /// #     account_info::{
302    /// #         AccountInfo,
303    /// #         next_account_info,
304    /// #     },
305    /// # };
306    /// // The custom instruction processed by our program. It includes the
307    /// // PDA's bump seed, which is derived by the client program. This
308    /// // definition is also imported into the off-chain client program.
309    /// // The computed address of the PDA will be passed to this program via
310    /// // the `accounts` vector of the `Instruction` type.
311    /// #[derive(BorshSerialize, BorshDeserialize, Debug)]
312    /// pub struct InstructionData {
313    ///     pub vault_bump_seed: u8,
314    ///     pub scoobies: u64,
315    /// }
316    ///
317    /// // The size in bytes of a vault account. The client program needs
318    /// // this information to calculate the quantity of scoobies necessary
319    /// // to pay for the account's rent.
320    /// pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
321    ///
322    /// // The entrypoint of the on-chain program, as provided to the
323    /// // `entrypoint!` macro.
324    /// fn process_instruction(
325    ///     program_id: &Pubkey,
326    ///     accounts: &[AccountInfo],
327    ///     instruction_data: &[u8],
328    /// ) -> ProgramResult {
329    ///     let account_info_iter = &mut accounts.iter();
330    ///     let payer = next_account_info(account_info_iter)?;
331    ///     // The vault PDA, derived from the payer's address
332    ///     let vault = next_account_info(account_info_iter)?;
333    ///
334    ///     let mut instruction_data = instruction_data;
335    ///     let instr = InstructionData::deserialize(&mut instruction_data)?;
336    ///     let vault_bump_seed = instr.vault_bump_seed;
337    ///     let scoobies = instr.scoobies;
338    ///     let vault_size = VAULT_ACCOUNT_SIZE;
339    ///
340    ///     // Invoke the system program to create an account while virtually
341    ///     // signing with the vault PDA, which is owned by this caller program.
342    ///     invoke_signed(
343    ///         &system_instruction::create_account(
344    ///             &payer.key,
345    ///             &vault.key,
346    ///             scoobies,
347    ///             vault_size,
348    ///             &program_id,
349    ///         ),
350    ///         &[
351    ///             payer.clone(),
352    ///             vault.clone(),
353    ///         ],
354    ///         // A slice of seed slices, each seed slice being the set
355    ///         // of seeds used to generate one of the PDAs required by the
356    ///         // callee program, the final seed being a single-element slice
357    ///         // containing the `u8` bump seed.
358    ///         &[
359    ///             &[
360    ///                 b"vault",
361    ///                 payer.key.as_ref(),
362    ///                 &[vault_bump_seed],
363    ///             ],
364    ///         ]
365    ///     )?;
366    ///
367    ///     Ok(())
368    /// }
369    /// ```
370    ///
371    /// The client program:
372    ///
373    /// ```
374    /// # use borsh::{BorshSerialize, BorshDeserialize};
375    /// # use cbe_program::example_mocks::{cbe_sdk, cbe_rpc_client};
376    /// # use cbe_program::{
377    /// #     pubkey::Pubkey,
378    /// #     instruction::Instruction,
379    /// #     hash::Hash,
380    /// #     instruction::AccountMeta,
381    /// #     system_program,
382    /// # };
383    /// # use cbe_sdk::{
384    /// #     signature::Keypair,
385    /// #     signature::{Signer, Signature},
386    /// #     transaction::Transaction,
387    /// # };
388    /// # use cbe_rpc_client::rpc_client::RpcClient;
389    /// # use std::convert::TryFrom;
390    /// # use anyhow::Result;
391    /// #
392    /// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
393    /// # struct InstructionData {
394    /// #    pub vault_bump_seed: u8,
395    /// #    pub scoobies: u64,
396    /// # }
397    /// #
398    /// # pub static VAULT_ACCOUNT_SIZE: u64 = 1024;
399    /// #
400    /// fn create_vault_account(
401    ///     client: &RpcClient,
402    ///     program_id: Pubkey,
403    ///     payer: &Keypair,
404    /// ) -> Result<()> {
405    ///     // Derive the PDA from the payer account, a string representing the unique
406    ///     // purpose of the account ("vault"), and the address of our on-chain program.
407    ///     let (vault_pubkey, vault_bump_seed) = Pubkey::find_program_address(
408    ///         &[b"vault", payer.pubkey().as_ref()],
409    ///         &program_id
410    ///     );
411    ///
412    ///     // Get the amount of scoobies needed to pay for the vault's rent
413    ///     let vault_account_size = usize::try_from(VAULT_ACCOUNT_SIZE)?;
414    ///     let scoobies = client.get_minimum_balance_for_rent_exemption(vault_account_size)?;
415    ///
416    ///     // The on-chain program's instruction data, imported from that program's crate.
417    ///     let instr_data = InstructionData {
418    ///         vault_bump_seed,
419    ///         scoobies,
420    ///     };
421    ///
422    ///     // The accounts required by both our on-chain program and the system program's
423    ///     // `create_account` instruction, including the vault's address.
424    ///     let accounts = vec![
425    ///         AccountMeta::new(payer.pubkey(), true),
426    ///         AccountMeta::new(vault_pubkey, false),
427    ///         AccountMeta::new(system_program::ID, false),
428    ///     ];
429    ///
430    ///     // Create the instruction by serializing our instruction data via borsh
431    ///     let instruction = Instruction::new_with_borsh(
432    ///         program_id,
433    ///         &instr_data,
434    ///         accounts,
435    ///     );
436    ///
437    ///     let blockhash = client.get_latest_blockhash()?;
438    ///
439    ///     let transaction = Transaction::new_signed_with_payer(
440    ///         &[instruction],
441    ///         Some(&payer.pubkey()),
442    ///         &[payer],
443    ///         blockhash,
444    ///     );
445    ///
446    ///     client.send_and_confirm_transaction(&transaction)?;
447    ///
448    ///     Ok(())
449    /// }
450    /// # let program_id = Pubkey::new_unique();
451    /// # let payer = Keypair::new();
452    /// # let client = RpcClient::new(String::new());
453    /// #
454    /// # create_vault_account(&client, program_id, &payer)?;
455    /// #
456    /// # Ok::<(), anyhow::Error>(())
457    /// ```
458    pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
459        Self::try_find_program_address(seeds, program_id)
460            .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
461    }
462
463    /// Find a valid [program derived address][pda] and its corresponding bump seed.
464    ///
465    /// [pda]: https://docs.cartallum.com/developing/programming-model/calling-between-programs#program-derived-addresses
466    ///
467    /// The only difference between this method and [`find_program_address`]
468    /// is that this one returns `None` in the statistically improbable event
469    /// that a bump seed cannot be found; or if any of `find_program_address`'s
470    /// preconditions are violated.
471    ///
472    /// See the documentation for [`find_program_address`] for a full description.
473    ///
474    /// [`find_program_address`]: Pubkey::find_program_address
475    #[allow(clippy::same_item_push)]
476    pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
477        // Perform the calculation inline, calling this from within a program is
478        // not supported
479        #[cfg(not(target_os = "cbe"))]
480        {
481            let mut bump_seed = [std::u8::MAX];
482            for _ in 0..std::u8::MAX {
483                {
484                    let mut seeds_with_bump = seeds.to_vec();
485                    seeds_with_bump.push(&bump_seed);
486                    match Self::create_program_address(&seeds_with_bump, program_id) {
487                        Ok(address) => return Some((address, bump_seed[0])),
488                        Err(PubkeyError::InvalidSeeds) => (),
489                        _ => break,
490                    }
491                }
492                bump_seed[0] -= 1;
493            }
494            None
495        }
496        // Call via a system call to perform the calculation
497        #[cfg(target_os = "cbe")]
498        {
499            let mut bytes = [0; 32];
500            let mut bump_seed = std::u8::MAX;
501            let result = unsafe {
502                crate::syscalls::cbe_try_find_program_address(
503                    seeds as *const _ as *const u8,
504                    seeds.len() as u64,
505                    program_id as *const _ as *const u8,
506                    &mut bytes as *mut _ as *mut u8,
507                    &mut bump_seed as *mut _ as *mut u8,
508                )
509            };
510            match result {
511                crate::entrypoint::SUCCESS => Some((Pubkey::new(&bytes), bump_seed)),
512                _ => None,
513            }
514        }
515    }
516
517    /// Create a valid [program derived address][pda] without searching for a bump seed.
518    ///
519    /// [pda]: https://docs.cartallum.com/developing/programming-model/calling-between-programs#program-derived-addresses
520    ///
521    /// Because this function does not create a bump seed, it may unpredictably
522    /// return an error for any given set of seeds and is not generally suitable
523    /// for creating program derived addresses.
524    ///
525    /// However, it can be used for efficiently verifying that a set of seeds plus
526    /// bump seed generated by [`find_program_address`] derives a particular
527    /// address as expected. See the example for details.
528    ///
529    /// See the documentation for [`find_program_address`] for a full description
530    /// of program derived addresses and bump seeds.
531    ///
532    /// [`find_program_address`]: Pubkey::find_program_address
533    ///
534    /// # Examples
535    ///
536    /// Creating a program derived address involves iteratively searching for a
537    /// bump seed for which the derived [`Pubkey`] does not lie on the ed25519
538    /// curve. This search process is generally performed off-chain, with the
539    /// [`find_program_address`] function, after which the client passes the
540    /// bump seed to the program as instruction data.
541    ///
542    /// Depending on the application requirements, a program may wish to verify
543    /// that the set of seeds, plus the bump seed, do correctly generate an
544    /// expected address.
545    ///
546    /// The verification is performed by appending to the other seeds one
547    /// additional seed slice that contains the single `u8` bump seed, calling
548    /// `create_program_address`, checking that the return value is `Ok`, and
549    /// that the returned `Pubkey` has the expected value.
550    ///
551    /// ```
552    /// # use cbe_program::pubkey::Pubkey;
553    /// # let program_id = Pubkey::new_unique();
554    /// let (expected_pda, bump_seed) = Pubkey::find_program_address(&[b"vault"], &program_id);
555    /// let actual_pda = Pubkey::create_program_address(&[b"vault", &[bump_seed]], &program_id)?;
556    /// assert_eq!(expected_pda, actual_pda);
557    /// # Ok::<(), anyhow::Error>(())
558    /// ```
559    pub fn create_program_address(
560        seeds: &[&[u8]],
561        program_id: &Pubkey,
562    ) -> Result<Pubkey, PubkeyError> {
563        if seeds.len() > MAX_SEEDS {
564            return Err(PubkeyError::MaxSeedLengthExceeded);
565        }
566        for seed in seeds.iter() {
567            if seed.len() > MAX_SEED_LEN {
568                return Err(PubkeyError::MaxSeedLengthExceeded);
569            }
570        }
571
572        // Perform the calculation inline, calling this from within a program is
573        // not supported
574        #[cfg(not(target_os = "cbe"))]
575        {
576            let mut hasher = crate::hash::Hasher::default();
577            for seed in seeds.iter() {
578                hasher.hash(seed);
579            }
580            hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
581            let hash = hasher.result();
582
583            if bytes_are_curve_point(hash) {
584                return Err(PubkeyError::InvalidSeeds);
585            }
586
587            Ok(Pubkey::new(hash.as_ref()))
588        }
589        // Call via a system call to perform the calculation
590        #[cfg(target_os = "cbe")]
591        {
592            let mut bytes = [0; 32];
593            let result = unsafe {
594                crate::syscalls::cbe_create_program_address(
595                    seeds as *const _ as *const u8,
596                    seeds.len() as u64,
597                    program_id as *const _ as *const u8,
598                    &mut bytes as *mut _ as *mut u8,
599                )
600            };
601            match result {
602                crate::entrypoint::SUCCESS => Ok(Pubkey::new(&bytes)),
603                _ => Err(result.into()),
604            }
605        }
606    }
607
608    pub fn to_bytes(self) -> [u8; 32] {
609        self.0
610    }
611
612    pub fn is_on_curve(&self) -> bool {
613        bytes_are_curve_point(self)
614    }
615
616    /// Log a `Pubkey` from a program
617    pub fn log(&self) {
618        #[cfg(target_os = "cbe")]
619        unsafe {
620            crate::syscalls::cbe_log_pubkey(self.as_ref() as *const _ as *const u8)
621        };
622
623        #[cfg(not(target_os = "cbe"))]
624        crate::program_stubs::cbe_log(&self.to_string());
625    }
626}
627
628impl AsRef<[u8]> for Pubkey {
629    fn as_ref(&self) -> &[u8] {
630        &self.0[..]
631    }
632}
633
634impl AsMut<[u8]> for Pubkey {
635    fn as_mut(&mut self) -> &mut [u8] {
636        &mut self.0[..]
637    }
638}
639
640impl fmt::Debug for Pubkey {
641    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
642        write!(f, "{}", bs58::encode(self.0).into_string())
643    }
644}
645
646impl fmt::Display for Pubkey {
647    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
648        write!(f, "{}", bs58::encode(self.0).into_string())
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use {super::*, std::str::from_utf8};
655
656    #[test]
657    fn test_new_unique() {
658        assert!(Pubkey::new_unique() != Pubkey::new_unique());
659    }
660
661    #[test]
662    fn pubkey_fromstr() {
663        let pubkey = Pubkey::new_unique();
664        let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
665
666        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
667
668        pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
669        assert_eq!(
670            pubkey_base58_str.parse::<Pubkey>(),
671            Err(ParsePubkeyError::WrongSize)
672        );
673
674        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
675        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
676
677        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
678        assert_eq!(
679            pubkey_base58_str.parse::<Pubkey>(),
680            Err(ParsePubkeyError::WrongSize)
681        );
682
683        let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
684        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
685
686        // throw some non-base58 stuff in there
687        pubkey_base58_str.replace_range(..1, "I");
688        assert_eq!(
689            pubkey_base58_str.parse::<Pubkey>(),
690            Err(ParsePubkeyError::Invalid)
691        );
692
693        // too long input string
694        // longest valid encoding
695        let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
696        // and one to grow on
697        too_long.push('1');
698        assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
699    }
700
701    #[test]
702    fn test_create_with_seed() {
703        assert!(
704            Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
705        );
706        assert_eq!(
707            Pubkey::create_with_seed(
708                &Pubkey::new_unique(),
709                from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
710                &Pubkey::new_unique()
711            ),
712            Err(PubkeyError::MaxSeedLengthExceeded)
713        );
714        assert!(Pubkey::create_with_seed(
715            &Pubkey::new_unique(),
716            "\
717             \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
718             ",
719            &Pubkey::new_unique()
720        )
721        .is_ok());
722        // utf-8 abuse ;)
723        assert_eq!(
724            Pubkey::create_with_seed(
725                &Pubkey::new_unique(),
726                "\
727                 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
728                 ",
729                &Pubkey::new_unique()
730            ),
731            Err(PubkeyError::MaxSeedLengthExceeded)
732        );
733
734        assert!(Pubkey::create_with_seed(
735            &Pubkey::new_unique(),
736            std::str::from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
737            &Pubkey::new_unique(),
738        )
739        .is_ok());
740
741        assert!(
742            Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
743        );
744
745        assert_eq!(
746            Pubkey::create_with_seed(
747                &Pubkey::default(),
748                "limber chicken: 4/45",
749                &Pubkey::default(),
750            ),
751            Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
752                .parse()
753                .unwrap())
754        );
755    }
756
757    #[test]
758    fn test_create_program_address() {
759        let exceeded_seed = &[127; MAX_SEED_LEN + 1];
760        let max_seed = &[0; MAX_SEED_LEN];
761        let exceeded_seeds: &[&[u8]] = &[
762            &[1],
763            &[2],
764            &[3],
765            &[4],
766            &[5],
767            &[6],
768            &[7],
769            &[8],
770            &[9],
771            &[10],
772            &[11],
773            &[12],
774            &[13],
775            &[14],
776            &[15],
777            &[16],
778            &[17],
779        ];
780        let max_seeds: &[&[u8]] = &[
781            &[1],
782            &[2],
783            &[3],
784            &[4],
785            &[5],
786            &[6],
787            &[7],
788            &[8],
789            &[9],
790            &[10],
791            &[11],
792            &[12],
793            &[13],
794            &[14],
795            &[15],
796            &[16],
797        ];
798        let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
799        let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
800
801        assert_eq!(
802            Pubkey::create_program_address(&[exceeded_seed], &program_id),
803            Err(PubkeyError::MaxSeedLengthExceeded)
804        );
805        assert_eq!(
806            Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
807            Err(PubkeyError::MaxSeedLengthExceeded)
808        );
809        assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
810        assert_eq!(
811            Pubkey::create_program_address(exceeded_seeds, &program_id),
812            Err(PubkeyError::MaxSeedLengthExceeded)
813        );
814        assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
815        assert_eq!(
816            Pubkey::create_program_address(&[b"", &[1]], &program_id),
817            Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
818                .parse()
819                .unwrap())
820        );
821        assert_eq!(
822            Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
823            Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
824                .parse()
825                .unwrap())
826        );
827        assert_eq!(
828            Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
829            Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
830                .parse()
831                .unwrap())
832        );
833        assert_eq!(
834            Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
835            Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
836                .parse()
837                .unwrap())
838        );
839        assert_ne!(
840            Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
841            Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
842        );
843    }
844
845    #[test]
846    fn test_pubkey_off_curve() {
847        // try a bunch of random input, all successful generated program
848        // addresses must land off the curve and be unique
849        let mut addresses = vec![];
850        for _ in 0..1_000 {
851            let program_id = Pubkey::new_unique();
852            let bytes1 = rand::random::<[u8; 10]>();
853            let bytes2 = rand::random::<[u8; 32]>();
854            if let Ok(program_address) =
855                Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
856            {
857                let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
858                    &program_address.to_bytes(),
859                )
860                .decompress()
861                .is_some();
862                assert!(!is_on_curve);
863                assert!(!addresses.contains(&program_address));
864                addresses.push(program_address);
865            }
866        }
867    }
868
869    #[test]
870    fn test_find_program_address() {
871        for _ in 0..1_000 {
872            let program_id = Pubkey::new_unique();
873            let (address, bump_seed) =
874                Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
875            assert_eq!(
876                address,
877                Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
878                    .unwrap()
879            );
880        }
881    }
882
883    fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
884        let key = Pubkey::new_unique();
885        let owner = Pubkey::default();
886
887        let mut to_fake = owner.to_bytes().to_vec();
888        to_fake.extend_from_slice(marker);
889
890        let seed = &String::from_utf8(to_fake[..to_fake.len() - 32].to_vec()).expect("not utf8");
891        let base = &Pubkey::try_from_slice(&to_fake[to_fake.len() - 32..]).unwrap();
892
893        Pubkey::create_with_seed(&key, seed, base)
894    }
895
896    #[test]
897    fn test_create_with_seed_rejects_illegal_owner() {
898        assert_eq!(
899            pubkey_from_seed_by_marker(PDA_MARKER),
900            Err(PubkeyError::IllegalOwner)
901        );
902        assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
903    }
904}