gemachain_program/
pubkey.rs

1#![allow(clippy::integer_arithmetic)]
2use crate::{decode_error::DecodeError, hash::hashv};
3
4use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
5use num_derive::{FromPrimitive, ToPrimitive};
6use std::{
7    convert::{Infallible, TryFrom},
8    fmt, mem,
9    str::FromStr,
10};
11use thiserror::Error;
12
13/// Number of bytes in a pubkey
14pub const PUBKEY_BYTES: usize = 32;
15/// maximum length of derived `Pubkey` seed
16pub const MAX_SEED_LEN: usize = 32;
17/// Maximum number of seeds
18pub const MAX_SEEDS: usize = 16;
19/// Maximum string length of a base58 encoded pubkey
20const MAX_BASE58_LEN: usize = 44;
21
22const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
23
24#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
25pub enum PubkeyError {
26    /// Length of the seed is too long for address generation
27    #[error("Length of the seed is too long for address generation")]
28    MaxSeedLengthExceeded,
29    #[error("Provided seeds do not result in a valid address")]
30    InvalidSeeds,
31    #[error("Provided owner is not allowed")]
32    IllegalOwner,
33}
34impl<T> DecodeError<T> for PubkeyError {
35    fn type_of() -> &'static str {
36        "PubkeyError"
37    }
38}
39impl From<u64> for PubkeyError {
40    fn from(error: u64) -> Self {
41        match error {
42            0 => PubkeyError::MaxSeedLengthExceeded,
43            1 => PubkeyError::InvalidSeeds,
44            _ => panic!("Unsupported PubkeyError"),
45        }
46    }
47}
48
49#[repr(transparent)]
50#[derive(
51    Serialize,
52    Deserialize,
53    BorshSerialize,
54    BorshDeserialize,
55    BorshSchema,
56    Clone,
57    Copy,
58    Default,
59    Eq,
60    PartialEq,
61    Ord,
62    PartialOrd,
63    Hash,
64    AbiExample,
65)]
66pub struct Pubkey([u8; 32]);
67
68impl crate::sanitize::Sanitize for Pubkey {}
69
70#[derive(Error, Debug, Serialize, Clone, PartialEq, FromPrimitive, ToPrimitive)]
71pub enum ParsePubkeyError {
72    #[error("String is the wrong size")]
73    WrongSize,
74    #[error("Invalid Base58 string")]
75    Invalid,
76}
77
78impl From<Infallible> for ParsePubkeyError {
79    fn from(_: Infallible) -> Self {
80        unreachable!("Infallible unihnabited");
81    }
82}
83
84impl<T> DecodeError<T> for ParsePubkeyError {
85    fn type_of() -> &'static str {
86        "ParsePubkeyError"
87    }
88}
89
90impl FromStr for Pubkey {
91    type Err = ParsePubkeyError;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        if s.len() > MAX_BASE58_LEN {
95            return Err(ParsePubkeyError::WrongSize);
96        }
97        let pubkey_vec = bs58::decode(s)
98            .into_vec()
99            .map_err(|_| ParsePubkeyError::Invalid)?;
100        if pubkey_vec.len() != mem::size_of::<Pubkey>() {
101            Err(ParsePubkeyError::WrongSize)
102        } else {
103            Ok(Pubkey::new(&pubkey_vec))
104        }
105    }
106}
107
108impl TryFrom<&str> for Pubkey {
109    type Error = ParsePubkeyError;
110    fn try_from(s: &str) -> Result<Self, Self::Error> {
111        Pubkey::from_str(s)
112    }
113}
114
115pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
116    #[cfg(not(target_arch = "bpf"))]
117    {
118        curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
119            .decompress()
120            .is_some()
121    }
122    #[cfg(target_arch = "bpf")]
123    unimplemented!();
124}
125
126impl Pubkey {
127    pub fn new(pubkey_vec: &[u8]) -> Self {
128        Self(
129            <[u8; 32]>::try_from(<&[u8]>::clone(&pubkey_vec))
130                .expect("Slice must be the same length as a Pubkey"),
131        )
132    }
133
134    pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
135        Self(pubkey_array)
136    }
137
138    #[deprecated(since = "1.3.9", note = "Please use 'Pubkey::new_unique' instead")]
139    #[cfg(not(target_arch = "bpf"))]
140    pub fn new_rand() -> Self {
141        // Consider removing Pubkey::new_rand() entirely in the v1.5 or v1.6 timeframe
142        Pubkey::new(&rand::random::<[u8; 32]>())
143    }
144
145    /// unique Pubkey for tests and benchmarks.
146    pub fn new_unique() -> Self {
147        use std::sync::atomic::{AtomicU64, Ordering};
148        static I: AtomicU64 = AtomicU64::new(1);
149
150        let mut b = [0u8; 32];
151        let i = I.fetch_add(1, Ordering::Relaxed);
152        b[0..8].copy_from_slice(&i.to_le_bytes());
153        Self::new(&b)
154    }
155
156    pub fn create_with_seed(
157        base: &Pubkey,
158        seed: &str,
159        owner: &Pubkey,
160    ) -> Result<Pubkey, PubkeyError> {
161        if seed.len() > MAX_SEED_LEN {
162            return Err(PubkeyError::MaxSeedLengthExceeded);
163        }
164
165        let owner = owner.as_ref();
166        if owner.len() >= PDA_MARKER.len() {
167            let slice = &owner[owner.len() - PDA_MARKER.len()..];
168            if slice == PDA_MARKER {
169                return Err(PubkeyError::IllegalOwner);
170            }
171        }
172
173        Ok(Pubkey::new(
174            hashv(&[base.as_ref(), seed.as_ref(), owner]).as_ref(),
175        ))
176    }
177
178    /// Create a program address
179    ///
180    /// Program addresses are account keys that only the program has the
181    /// authority to sign.  The address is of the same form as a Gemachain
182    /// `Pubkey`, except they are ensured to not be on the ed25519 curve and
183    /// thus have no associated private key.  When performing cross-program
184    /// invocations the program can "sign" for the key by calling
185    /// `invoke_signed` and passing the same seeds used to generate the address.
186    /// The runtime will check that indeed the program associated with this
187    /// address is the caller and thus authorized to be the signer.
188    ///
189    /// Because the program address cannot lie on the ed25519 curve there may be
190    /// seed and program id combinations that are invalid.  In these cases an
191    /// extra seed (bump seed) can be calculated that results in a point off the
192    /// curve.  Use `find_program_address` to calculate that bump seed.
193    ///
194    /// Warning: Because of the way the seeds are hashed there is a potential
195    /// for program address collisions for the same program id.  The seeds are
196    /// hashed sequentially which means that seeds {"abcdef"}, {"abc", "def"},
197    /// and {"ab", "cd", "ef"} will all result in the same program address given
198    /// the same program id.  Since the change of collision is local to a given
199    /// program id the developer of that program must take care to choose seeds
200    /// that do not collide with themselves.
201    pub fn create_program_address(
202        seeds: &[&[u8]],
203        program_id: &Pubkey,
204    ) -> Result<Pubkey, PubkeyError> {
205        if seeds.len() > MAX_SEEDS {
206            return Err(PubkeyError::MaxSeedLengthExceeded);
207        }
208        for seed in seeds.iter() {
209            if seed.len() > MAX_SEED_LEN {
210                return Err(PubkeyError::MaxSeedLengthExceeded);
211            }
212        }
213
214        // Perform the calculation inline, calling this from within a program is
215        // not supported
216        #[cfg(not(target_arch = "bpf"))]
217        {
218            let mut hasher = crate::hash::Hasher::default();
219            for seed in seeds.iter() {
220                hasher.hash(seed);
221            }
222            hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
223            let hash = hasher.result();
224
225            if bytes_are_curve_point(hash) {
226                return Err(PubkeyError::InvalidSeeds);
227            }
228
229            Ok(Pubkey::new(hash.as_ref()))
230        }
231        // Call via a system call to perform the calculation
232        #[cfg(target_arch = "bpf")]
233        {
234            extern "C" {
235                fn gema_create_program_address(
236                    seeds_addr: *const u8,
237                    seeds_len: u64,
238                    program_id_addr: *const u8,
239                    address_bytes_addr: *const u8,
240                ) -> u64;
241            }
242            let mut bytes = [0; 32];
243            let result = unsafe {
244                gema_create_program_address(
245                    seeds as *const _ as *const u8,
246                    seeds.len() as u64,
247                    program_id as *const _ as *const u8,
248                    &mut bytes as *mut _ as *mut u8,
249                )
250            };
251            match result {
252                crate::entrypoint::SUCCESS => Ok(Pubkey::new(&bytes)),
253                _ => Err(result.into()),
254            }
255        }
256    }
257
258    /// Find a valid program address and its corresponding bump seed which must
259    /// be passed as an additional seed when calling `invoke_signed`.
260    ///
261    /// Panics in the very unlikely event that the additional seed could not be
262    /// found.
263    ///
264    /// The processes of finding a valid program address is by trial and error,
265    /// and even though it is deterministic given a set of inputs it can take a
266    /// variable amount of time to succeed across different inputs.  This means
267    /// that when called from an on-chain program it may incur a variable amount
268    /// of the program's compute budget.  Programs that are meant to be very
269    /// performant may not want to use this function because it could take a
270    /// considerable amount of time.  Also, programs that area already at risk
271    /// of exceeding their compute budget should also call this with care since
272    /// there is a chance that the program's budget may be occasionally
273    /// exceeded.
274    pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
275        Self::try_find_program_address(seeds, program_id)
276            .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
277    }
278
279    /// Find a valid program address and its corresponding bump seed which must
280    /// be passed as an additional seed when calling `invoke_signed`.
281    ///
282    /// The processes of finding a valid program address is by trial and error,
283    /// and even though it is deterministic given a set of inputs it can take a
284    /// variable amount of time to succeed across different inputs.  This means
285    /// that when called from an on-chain program it may incur a variable amount
286    /// of the program's compute budget.  Programs that are meant to be very
287    /// performant may not want to use this function because it could take a
288    /// considerable amount of time.  Also, programs that area already at risk
289    /// of exceeding their compute budget should also call this with care since
290    /// there is a chance that the program's budget may be occasionally
291    /// exceeded.
292    #[allow(clippy::same_item_push)]
293    pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
294        // Perform the calculation inline, calling this from within a program is
295        // not supported
296        #[cfg(not(target_arch = "bpf"))]
297        {
298            let mut bump_seed = [std::u8::MAX];
299            for _ in 0..std::u8::MAX {
300                {
301                    let mut seeds_with_bump = seeds.to_vec();
302                    seeds_with_bump.push(&bump_seed);
303                    match Self::create_program_address(&seeds_with_bump, program_id) {
304                        Ok(address) => return Some((address, bump_seed[0])),
305                        Err(PubkeyError::InvalidSeeds) => (),
306                        _ => break,
307                    }
308                }
309                bump_seed[0] -= 1;
310            }
311            None
312        }
313        // Call via a system call to perform the calculation
314        #[cfg(target_arch = "bpf")]
315        {
316            extern "C" {
317                fn gema_try_find_program_address(
318                    seeds_addr: *const u8,
319                    seeds_len: u64,
320                    program_id_addr: *const u8,
321                    address_bytes_addr: *const u8,
322                    bump_seed_addr: *const u8,
323                ) -> u64;
324            }
325            let mut bytes = [0; 32];
326            let mut bump_seed = std::u8::MAX;
327            let result = unsafe {
328                gema_try_find_program_address(
329                    seeds as *const _ as *const u8,
330                    seeds.len() as u64,
331                    program_id as *const _ as *const u8,
332                    &mut bytes as *mut _ as *mut u8,
333                    &mut bump_seed as *mut _ as *mut u8,
334                )
335            };
336            match result {
337                crate::entrypoint::SUCCESS => Some((Pubkey::new(&bytes), bump_seed)),
338                _ => None,
339            }
340        }
341    }
342
343    pub fn to_bytes(self) -> [u8; 32] {
344        self.0
345    }
346
347    pub fn is_on_curve(&self) -> bool {
348        bytes_are_curve_point(self)
349    }
350
351    /// Log a `Pubkey` from a program
352    pub fn log(&self) {
353        #[cfg(target_arch = "bpf")]
354        {
355            extern "C" {
356                fn gema_log_pubkey(pubkey_addr: *const u8);
357            }
358            unsafe { gema_log_pubkey(self.as_ref() as *const _ as *const u8) };
359        }
360
361        #[cfg(not(target_arch = "bpf"))]
362        crate::program_stubs::gema_log(&self.to_string());
363    }
364}
365
366impl AsRef<[u8]> for Pubkey {
367    fn as_ref(&self) -> &[u8] {
368        &self.0[..]
369    }
370}
371
372impl AsMut<[u8]> for Pubkey {
373    fn as_mut(&mut self) -> &mut [u8] {
374        &mut self.0[..]
375    }
376}
377
378impl fmt::Debug for Pubkey {
379    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
380        write!(f, "{}", bs58::encode(self.0).into_string())
381    }
382}
383
384impl fmt::Display for Pubkey {
385    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386        write!(f, "{}", bs58::encode(self.0).into_string())
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use std::str::from_utf8;
394
395    #[test]
396    fn test_new_unique() {
397        assert!(Pubkey::new_unique() != Pubkey::new_unique());
398    }
399
400    #[test]
401    fn pubkey_fromstr() {
402        let pubkey = Pubkey::new_unique();
403        let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
404
405        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
406
407        pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
408        assert_eq!(
409            pubkey_base58_str.parse::<Pubkey>(),
410            Err(ParsePubkeyError::WrongSize)
411        );
412
413        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
414        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
415
416        pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
417        assert_eq!(
418            pubkey_base58_str.parse::<Pubkey>(),
419            Err(ParsePubkeyError::WrongSize)
420        );
421
422        let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
423        assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
424
425        // throw some non-base58 stuff in there
426        pubkey_base58_str.replace_range(..1, "I");
427        assert_eq!(
428            pubkey_base58_str.parse::<Pubkey>(),
429            Err(ParsePubkeyError::Invalid)
430        );
431
432        // too long input string
433        // longest valid encoding
434        let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
435        // and one to grow on
436        too_long.push('1');
437        assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
438    }
439
440    #[test]
441    fn test_create_with_seed() {
442        assert!(
443            Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
444        );
445        assert_eq!(
446            Pubkey::create_with_seed(
447                &Pubkey::new_unique(),
448                from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
449                &Pubkey::new_unique()
450            ),
451            Err(PubkeyError::MaxSeedLengthExceeded)
452        );
453        assert!(Pubkey::create_with_seed(
454            &Pubkey::new_unique(),
455            "\
456             \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
457             ",
458            &Pubkey::new_unique()
459        )
460        .is_ok());
461        // utf-8 abuse ;)
462        assert_eq!(
463            Pubkey::create_with_seed(
464                &Pubkey::new_unique(),
465                "\
466                 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
467                 ",
468                &Pubkey::new_unique()
469            ),
470            Err(PubkeyError::MaxSeedLengthExceeded)
471        );
472
473        assert!(Pubkey::create_with_seed(
474            &Pubkey::new_unique(),
475            std::str::from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
476            &Pubkey::new_unique(),
477        )
478        .is_ok());
479
480        assert!(
481            Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
482        );
483
484        assert_eq!(
485            Pubkey::create_with_seed(
486                &Pubkey::default(),
487                "limber chicken: 4/45",
488                &Pubkey::default(),
489            ),
490            Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
491                .parse()
492                .unwrap())
493        );
494    }
495
496    #[test]
497    fn test_create_program_address() {
498        let exceeded_seed = &[127; MAX_SEED_LEN + 1];
499        let max_seed = &[0; MAX_SEED_LEN];
500        let exceeded_seeds: &[&[u8]] = &[
501            &[1],
502            &[2],
503            &[3],
504            &[4],
505            &[5],
506            &[6],
507            &[7],
508            &[8],
509            &[9],
510            &[10],
511            &[11],
512            &[12],
513            &[13],
514            &[14],
515            &[15],
516            &[16],
517            &[17],
518        ];
519        let max_seeds: &[&[u8]] = &[
520            &[1],
521            &[2],
522            &[3],
523            &[4],
524            &[5],
525            &[6],
526            &[7],
527            &[8],
528            &[9],
529            &[10],
530            &[11],
531            &[12],
532            &[13],
533            &[14],
534            &[15],
535            &[16],
536        ];
537        let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
538        let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
539
540        assert_eq!(
541            Pubkey::create_program_address(&[exceeded_seed], &program_id),
542            Err(PubkeyError::MaxSeedLengthExceeded)
543        );
544        assert_eq!(
545            Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
546            Err(PubkeyError::MaxSeedLengthExceeded)
547        );
548        assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
549        assert_eq!(
550            Pubkey::create_program_address(exceeded_seeds, &program_id),
551            Err(PubkeyError::MaxSeedLengthExceeded)
552        );
553        assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
554        assert_eq!(
555            Pubkey::create_program_address(&[b"", &[1]], &program_id),
556            Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
557                .parse()
558                .unwrap())
559        );
560        assert_eq!(
561            Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
562            Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
563                .parse()
564                .unwrap())
565        );
566        assert_eq!(
567            Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
568            Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
569                .parse()
570                .unwrap())
571        );
572        assert_eq!(
573            Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
574            Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
575                .parse()
576                .unwrap())
577        );
578        assert_ne!(
579            Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
580            Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
581        );
582    }
583
584    #[test]
585    fn test_pubkey_off_curve() {
586        // try a bunch of random input, all successful generated program
587        // addresses must land off the curve and be unique
588        let mut addresses = vec![];
589        for _ in 0..1_000 {
590            let program_id = Pubkey::new_unique();
591            let bytes1 = rand::random::<[u8; 10]>();
592            let bytes2 = rand::random::<[u8; 32]>();
593            if let Ok(program_address) =
594                Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
595            {
596                let is_on_curve = curve25519_dalek::edwards::CompressedEdwardsY::from_slice(
597                    &program_address.to_bytes(),
598                )
599                .decompress()
600                .is_some();
601                assert!(!is_on_curve);
602                assert!(!addresses.contains(&program_address));
603                addresses.push(program_address);
604            }
605        }
606    }
607
608    #[test]
609    fn test_find_program_address() {
610        for _ in 0..1_000 {
611            let program_id = Pubkey::new_unique();
612            let (address, bump_seed) =
613                Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
614            assert_eq!(
615                address,
616                Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
617                    .unwrap()
618            );
619        }
620    }
621
622    fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
623        let key = Pubkey::new_unique();
624        let owner = Pubkey::default();
625
626        let mut to_fake = owner.to_bytes().to_vec();
627        to_fake.extend_from_slice(marker);
628
629        let seed = &String::from_utf8(to_fake[..to_fake.len() - 32].to_vec()).expect("not utf8");
630        let base = &Pubkey::try_from_slice(&to_fake[to_fake.len() - 32..]).unwrap();
631
632        Pubkey::create_with_seed(&key, seed, base)
633    }
634
635    #[test]
636    fn test_create_with_seed_rejects_illegal_owner() {
637        assert_eq!(
638            pubkey_from_seed_by_marker(PDA_MARKER),
639            Err(PubkeyError::IllegalOwner)
640        );
641        assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
642    }
643}