1#![no_std]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
4#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
5#![allow(clippy::arithmetic_side_effects)]
6
7#[cfg(any(feature = "std", target_arch = "wasm32"))]
8extern crate std;
9#[cfg(feature = "dev-context-only-utils")]
10use arbitrary::Arbitrary;
11#[cfg(feature = "bytemuck")]
12use bytemuck_derive::{Pod, Zeroable};
13#[cfg(feature = "serde")]
14use serde_derive::{Deserialize, Serialize};
15#[cfg(any(feature = "std", target_arch = "wasm32"))]
16use std::vec::Vec;
17#[cfg(feature = "borsh")]
18use {
19 borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
20 std::string::ToString,
21};
22use {
23 core::{
24 array,
25 convert::{Infallible, TryFrom},
26 fmt,
27 hash::{Hash, Hasher},
28 mem,
29 str::{from_utf8_unchecked, FromStr},
30 },
31 num_traits::{FromPrimitive, ToPrimitive},
32};
33#[cfg(target_arch = "wasm32")]
34use {
35 js_sys::{Array, Uint8Array},
36 wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue},
37};
38
39#[cfg(target_os = "solana")]
40pub mod syscalls;
41
42pub const PUBKEY_BYTES: usize = 32;
44pub const MAX_SEED_LEN: usize = 32;
46pub const MAX_SEEDS: usize = 16;
48const MAX_BASE58_LEN: usize = 44;
50
51#[cfg(any(target_os = "solana", feature = "sha2", feature = "curve25519"))]
52const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
53
54#[cfg(target_os = "solana")]
57const SUCCESS: u64 = 0;
58
59#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
62#[cfg_attr(feature = "serde", derive(Serialize))]
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum PubkeyError {
65 MaxSeedLengthExceeded,
67 InvalidSeeds,
68 IllegalOwner,
69}
70
71impl ToPrimitive for PubkeyError {
72 #[inline]
73 fn to_i64(&self) -> Option<i64> {
74 Some(match *self {
75 PubkeyError::MaxSeedLengthExceeded => PubkeyError::MaxSeedLengthExceeded as i64,
76 PubkeyError::InvalidSeeds => PubkeyError::InvalidSeeds as i64,
77 PubkeyError::IllegalOwner => PubkeyError::IllegalOwner as i64,
78 })
79 }
80 #[inline]
81 fn to_u64(&self) -> Option<u64> {
82 self.to_i64().map(|x| x as u64)
83 }
84}
85
86impl FromPrimitive for PubkeyError {
87 #[inline]
88 fn from_i64(n: i64) -> Option<Self> {
89 if n == PubkeyError::MaxSeedLengthExceeded as i64 {
90 Some(PubkeyError::MaxSeedLengthExceeded)
91 } else if n == PubkeyError::InvalidSeeds as i64 {
92 Some(PubkeyError::InvalidSeeds)
93 } else if n == PubkeyError::IllegalOwner as i64 {
94 Some(PubkeyError::IllegalOwner)
95 } else {
96 None
97 }
98 }
99 #[inline]
100 fn from_u64(n: u64) -> Option<Self> {
101 Self::from_i64(n as i64)
102 }
103}
104
105#[cfg(feature = "std")]
106impl std::error::Error for PubkeyError {}
107
108impl fmt::Display for PubkeyError {
109 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
110 match self {
111 PubkeyError::MaxSeedLengthExceeded => {
112 f.write_str("Length of the seed is too long for address generation")
113 }
114 PubkeyError::InvalidSeeds => {
115 f.write_str("Provided seeds do not result in a valid address")
116 }
117 PubkeyError::IllegalOwner => f.write_str("Provided owner is not allowed"),
118 }
119 }
120}
121
122#[allow(deprecated)]
123impl<T> solana_decode_error::DecodeError<T> for PubkeyError {
124 fn type_of() -> &'static str {
125 "PubkeyError"
126 }
127}
128impl From<u64> for PubkeyError {
129 fn from(error: u64) -> Self {
130 match error {
131 0 => PubkeyError::MaxSeedLengthExceeded,
132 1 => PubkeyError::InvalidSeeds,
133 2 => PubkeyError::IllegalOwner,
134 _ => panic!("Unsupported PubkeyError"),
135 }
136 }
137}
138
139#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
154#[repr(transparent)]
155#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))]
156#[cfg_attr(
157 feature = "borsh",
158 derive(BorshSerialize, BorshDeserialize),
159 borsh(crate = "borsh")
160)]
161#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
162#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
163#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
164#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
165#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
166pub struct Pubkey(pub(crate) [u8; 32]);
167
168impl Hash for Pubkey {
172 fn hash<H: Hasher>(&self, state: &mut H) {
173 state.write(self.as_array());
174 }
175}
176
177#[cfg(all(feature = "rand", not(target_os = "solana")))]
178mod hasher {
179 use {
180 crate::PUBKEY_BYTES,
181 core::{
182 cell::Cell,
183 hash::{BuildHasher, Hasher},
184 mem,
185 },
186 rand::{thread_rng, Rng},
187 };
188
189 #[derive(Default)]
197 pub struct PubkeyHasher {
198 offset: usize,
199 state: u64,
200 }
201
202 impl Hasher for PubkeyHasher {
203 #[inline]
204 fn finish(&self) -> u64 {
205 self.state
206 }
207 #[inline]
208 fn write(&mut self, bytes: &[u8]) {
209 debug_assert_eq!(
210 bytes.len(),
211 PUBKEY_BYTES,
212 "This hasher is intended to be used with pubkeys and nothing else"
213 );
214 let chunk: &[u8; mem::size_of::<u64>()] = bytes
216 [self.offset..self.offset + mem::size_of::<u64>()]
217 .try_into()
218 .unwrap();
219 self.state = u64::from_ne_bytes(*chunk);
220 }
221 }
222
223 #[derive(Clone)]
231 pub struct PubkeyHasherBuilder {
232 offset: usize,
233 }
234
235 impl Default for PubkeyHasherBuilder {
236 fn default() -> Self {
244 std::thread_local!(static OFFSET: Cell<usize> = {
245 let mut rng = thread_rng();
246 Cell::new(rng.gen_range(0..PUBKEY_BYTES - mem::size_of::<u64>()))
247 });
248
249 let offset = OFFSET.with(|offset| {
250 let mut next_offset = offset.get() + 1;
251 if next_offset > PUBKEY_BYTES - mem::size_of::<u64>() {
252 next_offset = 0;
253 }
254 offset.set(next_offset);
255 next_offset
256 });
257 PubkeyHasherBuilder { offset }
258 }
259 }
260
261 impl BuildHasher for PubkeyHasherBuilder {
262 type Hasher = PubkeyHasher;
263 #[inline]
264 fn build_hasher(&self) -> Self::Hasher {
265 PubkeyHasher {
266 offset: self.offset,
267 state: 0,
268 }
269 }
270 }
271
272 #[cfg(test)]
273 mod tests {
274 use {
275 super::PubkeyHasherBuilder,
276 crate::Pubkey,
277 core::hash::{BuildHasher, Hasher},
278 };
279 #[test]
280 fn test_pubkey_hasher_builder() {
281 let key = Pubkey::new_unique();
282 let builder = PubkeyHasherBuilder::default();
283 let mut hasher1 = builder.build_hasher();
284 let mut hasher2 = builder.build_hasher();
285 hasher1.write(key.as_array());
286 hasher2.write(key.as_array());
287 assert_eq!(
288 hasher1.finish(),
289 hasher2.finish(),
290 "Hashers made with same builder should be identical"
291 );
292 let builder2 = PubkeyHasherBuilder::default();
295 for _ in 0..64 {
296 let mut hasher3 = builder2.build_hasher();
297 hasher3.write(key.as_array());
298 std::dbg!(hasher1.finish());
299 std::dbg!(hasher3.finish());
300 if hasher1.finish() != hasher3.finish() {
301 return;
302 }
303 }
304 panic!("Hashers built with different builder should be different due to random offset");
305 }
306
307 #[test]
308 fn test_pubkey_hasher() {
309 let key1 = Pubkey::new_unique();
310 let key2 = Pubkey::new_unique();
311 let builder = PubkeyHasherBuilder::default();
312 let mut hasher1 = builder.build_hasher();
313 let mut hasher2 = builder.build_hasher();
314 hasher1.write(key1.as_array());
315 hasher2.write(key2.as_array());
316 assert_ne!(hasher1.finish(), hasher2.finish());
317 }
318 }
319}
320#[cfg(all(feature = "rand", not(target_os = "solana")))]
321pub use hasher::{PubkeyHasher, PubkeyHasherBuilder};
322
323impl solana_sanitize::Sanitize for Pubkey {}
324
325#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
328#[cfg_attr(feature = "serde", derive(Serialize))]
329#[derive(Debug, Clone, PartialEq, Eq)]
330pub enum ParsePubkeyError {
331 WrongSize,
332 Invalid,
333}
334
335impl ToPrimitive for ParsePubkeyError {
336 #[inline]
337 fn to_i64(&self) -> Option<i64> {
338 Some(match *self {
339 ParsePubkeyError::WrongSize => ParsePubkeyError::WrongSize as i64,
340 ParsePubkeyError::Invalid => ParsePubkeyError::Invalid as i64,
341 })
342 }
343 #[inline]
344 fn to_u64(&self) -> Option<u64> {
345 self.to_i64().map(|x| x as u64)
346 }
347}
348
349impl FromPrimitive for ParsePubkeyError {
350 #[inline]
351 fn from_i64(n: i64) -> Option<Self> {
352 if n == ParsePubkeyError::WrongSize as i64 {
353 Some(ParsePubkeyError::WrongSize)
354 } else if n == ParsePubkeyError::Invalid as i64 {
355 Some(ParsePubkeyError::Invalid)
356 } else {
357 None
358 }
359 }
360 #[inline]
361 fn from_u64(n: u64) -> Option<Self> {
362 Self::from_i64(n as i64)
363 }
364}
365
366#[cfg(feature = "std")]
367impl std::error::Error for ParsePubkeyError {}
368
369impl fmt::Display for ParsePubkeyError {
370 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
371 match self {
372 ParsePubkeyError::WrongSize => f.write_str("String is the wrong size"),
373 ParsePubkeyError::Invalid => f.write_str("Invalid Base58 string"),
374 }
375 }
376}
377
378impl From<Infallible> for ParsePubkeyError {
379 fn from(_: Infallible) -> Self {
380 unreachable!("Infallible uninhabited");
381 }
382}
383
384#[allow(deprecated)]
385impl<T> solana_decode_error::DecodeError<T> for ParsePubkeyError {
386 fn type_of() -> &'static str {
387 "ParsePubkeyError"
388 }
389}
390
391impl FromStr for Pubkey {
392 type Err = ParsePubkeyError;
393
394 fn from_str(s: &str) -> Result<Self, Self::Err> {
395 use five8::DecodeError;
396 if s.len() > MAX_BASE58_LEN {
397 return Err(ParsePubkeyError::WrongSize);
398 }
399 let mut bytes = [0; PUBKEY_BYTES];
400 five8::decode_32(s, &mut bytes).map_err(|e| match e {
401 DecodeError::InvalidChar(_) => ParsePubkeyError::Invalid,
402 DecodeError::TooLong
403 | DecodeError::TooShort
404 | DecodeError::LargestTermTooHigh
405 | DecodeError::OutputTooLong => ParsePubkeyError::WrongSize,
406 })?;
407 Ok(Pubkey(bytes))
408 }
409}
410
411impl From<&Pubkey> for Pubkey {
412 #[inline]
413 fn from(value: &Pubkey) -> Self {
414 *value
415 }
416}
417
418impl From<[u8; 32]> for Pubkey {
419 #[inline]
420 fn from(from: [u8; 32]) -> Self {
421 Self(from)
422 }
423}
424
425impl TryFrom<&[u8]> for Pubkey {
426 type Error = array::TryFromSliceError;
427
428 #[inline]
429 fn try_from(pubkey: &[u8]) -> Result<Self, Self::Error> {
430 <[u8; 32]>::try_from(pubkey).map(Self::from)
431 }
432}
433
434#[cfg(any(feature = "std", target_arch = "wasm32"))]
435impl TryFrom<Vec<u8>> for Pubkey {
436 type Error = Vec<u8>;
437
438 #[inline]
439 fn try_from(pubkey: Vec<u8>) -> Result<Self, Self::Error> {
440 <[u8; 32]>::try_from(pubkey).map(Self::from)
441 }
442}
443
444impl TryFrom<&str> for Pubkey {
445 type Error = ParsePubkeyError;
446 fn try_from(s: &str) -> Result<Self, Self::Error> {
447 Pubkey::from_str(s)
448 }
449}
450
451#[cfg(any(target_os = "solana", feature = "curve25519"))]
455#[allow(clippy::used_underscore_binding)]
456pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
457 #[cfg(not(target_os = "solana"))]
458 {
459 let Ok(compressed_edwards_y) =
460 curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
461 else {
462 return false;
463 };
464 compressed_edwards_y.decompress().is_some()
465 }
466 #[cfg(target_os = "solana")]
467 unimplemented!();
468}
469
470impl Pubkey {
471 pub const fn new_from_array(pubkey_array: [u8; 32]) -> Self {
472 Self(pubkey_array)
473 }
474
475 pub const fn from_str_const(s: &str) -> Self {
477 let id_array = five8_const::decode_32_const(s);
478 Pubkey::new_from_array(id_array)
479 }
480
481 pub fn new_unique() -> Self {
483 use solana_atomic_u64::AtomicU64;
484 static I: AtomicU64 = AtomicU64::new(1);
485 type T = u32;
486 const COUNTER_BYTES: usize = mem::size_of::<T>();
487 let mut b = [0u8; PUBKEY_BYTES];
488 #[cfg(any(feature = "std", target_arch = "wasm32"))]
489 let mut i = I.fetch_add(1) as T;
490 #[cfg(not(any(feature = "std", target_arch = "wasm32")))]
491 let i = I.fetch_add(1) as T;
492 b[0..COUNTER_BYTES].copy_from_slice(&i.to_be_bytes());
495 #[cfg(any(feature = "std", target_arch = "wasm32"))]
498 {
499 let mut hash = std::hash::DefaultHasher::new();
500 for slice in b[COUNTER_BYTES..].chunks_mut(COUNTER_BYTES) {
501 hash.write_u32(i);
502 i += 1;
503 slice.copy_from_slice(&hash.finish().to_ne_bytes()[0..COUNTER_BYTES]);
504 }
505 }
506 #[cfg(not(any(feature = "std", target_arch = "wasm32")))]
509 {
510 for b in b[COUNTER_BYTES..].iter_mut() {
511 *b = (i & 0xFF) as u8;
512 }
513 }
514 Self::from(b)
515 }
516
517 #[cfg(any(target_os = "solana", feature = "sha2"))]
522 pub fn create_with_seed(
523 base: &Pubkey,
524 seed: &str,
525 owner: &Pubkey,
526 ) -> Result<Pubkey, PubkeyError> {
527 if seed.len() > MAX_SEED_LEN {
528 return Err(PubkeyError::MaxSeedLengthExceeded);
529 }
530
531 let owner = owner.as_ref();
532 if owner.len() >= PDA_MARKER.len() {
533 let slice = &owner[owner.len() - PDA_MARKER.len()..];
534 if slice == PDA_MARKER {
535 return Err(PubkeyError::IllegalOwner);
536 }
537 }
538 let hash = solana_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
539 Ok(Pubkey::from(hash.to_bytes()))
540 }
541
542 #[cfg(any(target_os = "solana", feature = "curve25519"))]
800 pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
801 Self::try_find_program_address(seeds, program_id)
802 .unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
803 }
804
805 #[cfg(any(target_os = "solana", feature = "curve25519"))]
822 #[allow(clippy::same_item_push)]
823 pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
824 #[cfg(not(target_os = "solana"))]
827 {
828 let mut bump_seed = [u8::MAX];
829 for _ in 0..u8::MAX {
830 {
831 let mut seeds_with_bump = seeds.to_vec();
832 seeds_with_bump.push(&bump_seed);
833 match Self::create_program_address(&seeds_with_bump, program_id) {
834 Ok(address) => return Some((address, bump_seed[0])),
835 Err(PubkeyError::InvalidSeeds) => (),
836 _ => break,
837 }
838 }
839 bump_seed[0] -= 1;
840 }
841 None
842 }
843 #[cfg(target_os = "solana")]
845 {
846 let mut bytes = [0; 32];
847 let mut bump_seed = u8::MAX;
848 let result = unsafe {
849 crate::syscalls::sol_try_find_program_address(
850 seeds as *const _ as *const u8,
851 seeds.len() as u64,
852 program_id as *const _ as *const u8,
853 &mut bytes as *mut _ as *mut u8,
854 &mut bump_seed as *mut _ as *mut u8,
855 )
856 };
857 match result {
858 SUCCESS => Some((Pubkey::from(bytes), bump_seed)),
859 _ => None,
860 }
861 }
862 }
863
864 #[cfg(any(target_os = "solana", feature = "curve25519"))]
911 pub fn create_program_address(
912 seeds: &[&[u8]],
913 program_id: &Pubkey,
914 ) -> Result<Pubkey, PubkeyError> {
915 if seeds.len() > MAX_SEEDS {
916 return Err(PubkeyError::MaxSeedLengthExceeded);
917 }
918 for seed in seeds.iter() {
919 if seed.len() > MAX_SEED_LEN {
920 return Err(PubkeyError::MaxSeedLengthExceeded);
921 }
922 }
923
924 #[cfg(not(target_os = "solana"))]
927 {
928 let mut hasher = solana_sha256_hasher::Hasher::default();
929 for seed in seeds.iter() {
930 hasher.hash(seed);
931 }
932 hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
933 let hash = hasher.result();
934
935 if bytes_are_curve_point(hash) {
936 return Err(PubkeyError::InvalidSeeds);
937 }
938
939 Ok(Pubkey::from(hash.to_bytes()))
940 }
941 #[cfg(target_os = "solana")]
943 {
944 let mut bytes = [0; 32];
945 let result = unsafe {
946 crate::syscalls::sol_create_program_address(
947 seeds as *const _ as *const u8,
948 seeds.len() as u64,
949 program_id as *const _ as *const u8,
950 &mut bytes as *mut _ as *mut u8,
951 )
952 };
953 match result {
954 SUCCESS => Ok(Pubkey::from(bytes)),
955 _ => Err(result.into()),
956 }
957 }
958 }
959
960 pub const fn to_bytes(self) -> [u8; 32] {
961 self.0
962 }
963
964 #[inline(always)]
966 pub const fn as_array(&self) -> &[u8; 32] {
967 &self.0
968 }
969
970 #[cfg(any(target_os = "solana", feature = "curve25519"))]
974 pub fn is_on_curve(&self) -> bool {
975 bytes_are_curve_point(self)
976 }
977
978 pub fn log(&self) {
980 #[cfg(target_os = "solana")]
981 unsafe {
982 crate::syscalls::sol_log_pubkey(self.as_ref() as *const _ as *const u8)
983 };
984
985 #[cfg(all(not(target_os = "solana"), feature = "std"))]
986 std::println!("{}", std::string::ToString::to_string(&self));
987 }
988}
989
990impl AsRef<[u8]> for Pubkey {
991 fn as_ref(&self) -> &[u8] {
992 &self.0[..]
993 }
994}
995
996impl AsMut<[u8]> for Pubkey {
997 fn as_mut(&mut self) -> &mut [u8] {
998 &mut self.0[..]
999 }
1000}
1001
1002fn write_as_base58(f: &mut fmt::Formatter, p: &Pubkey) -> fmt::Result {
1003 let mut out = [0u8; MAX_BASE58_LEN];
1004 let len = five8::encode_32(&p.0, &mut out) as usize;
1005 let as_str = unsafe { from_utf8_unchecked(&out[..len]) };
1007 f.write_str(as_str)
1008}
1009
1010impl fmt::Debug for Pubkey {
1011 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1012 write_as_base58(f, self)
1013 }
1014}
1015
1016impl fmt::Display for Pubkey {
1017 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1018 write_as_base58(f, self)
1019 }
1020}
1021
1022#[cfg(feature = "borsh")]
1023impl borsh0_10::de::BorshDeserialize for Pubkey {
1024 fn deserialize_reader<R: borsh0_10::maybestd::io::Read>(
1025 reader: &mut R,
1026 ) -> Result<Self, borsh0_10::maybestd::io::Error> {
1027 Ok(Self(borsh0_10::BorshDeserialize::deserialize_reader(
1028 reader,
1029 )?))
1030 }
1031}
1032
1033#[cfg(feature = "borsh")]
1034macro_rules! impl_borsh_schema {
1035 ($borsh:ident) => {
1036 impl $borsh::BorshSchema for Pubkey
1037 where
1038 [u8; 32]: $borsh::BorshSchema,
1039 {
1040 fn declaration() -> $borsh::schema::Declaration {
1041 std::string::String::from("Pubkey")
1042 }
1043 fn add_definitions_recursively(
1044 definitions: &mut $borsh::maybestd::collections::HashMap<
1045 $borsh::schema::Declaration,
1046 $borsh::schema::Definition,
1047 >,
1048 ) {
1049 let fields = $borsh::schema::Fields::UnnamedFields(<[_]>::into_vec(
1050 $borsh::maybestd::boxed::Box::new([
1051 <[u8; 32] as $borsh::BorshSchema>::declaration(),
1052 ]),
1053 ));
1054 let definition = $borsh::schema::Definition::Struct { fields };
1055 <Self as $borsh::BorshSchema>::add_definition(
1056 <Self as $borsh::BorshSchema>::declaration(),
1057 definition,
1058 definitions,
1059 );
1060 <[u8; 32] as $borsh::BorshSchema>::add_definitions_recursively(definitions);
1061 }
1062 }
1063 };
1064}
1065#[cfg(feature = "borsh")]
1066impl_borsh_schema!(borsh0_10);
1067
1068#[cfg(feature = "borsh")]
1069macro_rules! impl_borsh_serialize {
1070 ($borsh:ident) => {
1071 impl $borsh::ser::BorshSerialize for Pubkey {
1072 fn serialize<W: $borsh::maybestd::io::Write>(
1073 &self,
1074 writer: &mut W,
1075 ) -> ::core::result::Result<(), $borsh::maybestd::io::Error> {
1076 $borsh::BorshSerialize::serialize(&self.0, writer)?;
1077 Ok(())
1078 }
1079 }
1080 };
1081}
1082#[cfg(feature = "borsh")]
1083impl_borsh_serialize!(borsh0_10);
1084
1085#[cfg(all(target_arch = "wasm32", feature = "curve25519"))]
1086fn js_value_to_seeds_vec(array_of_uint8_arrays: &[JsValue]) -> Result<Vec<Vec<u8>>, JsValue> {
1087 let vec_vec_u8 = array_of_uint8_arrays
1088 .iter()
1089 .filter_map(|u8_array| {
1090 u8_array
1091 .dyn_ref::<Uint8Array>()
1092 .map(|u8_array| u8_array.to_vec())
1093 })
1094 .collect::<Vec<_>>();
1095
1096 if vec_vec_u8.len() != array_of_uint8_arrays.len() {
1097 Err("Invalid Array of Uint8Arrays".into())
1098 } else {
1099 Ok(vec_vec_u8)
1100 }
1101}
1102
1103#[cfg(target_arch = "wasm32")]
1104fn display_to_jsvalue<T: fmt::Display>(display: T) -> JsValue {
1105 std::string::ToString::to_string(&display).into()
1106}
1107
1108#[allow(non_snake_case)]
1109#[cfg(target_arch = "wasm32")]
1110#[wasm_bindgen]
1111impl Pubkey {
1112 #[wasm_bindgen(constructor)]
1116 pub fn constructor(value: JsValue) -> Result<Pubkey, JsValue> {
1117 if let Some(base58_str) = value.as_string() {
1118 base58_str.parse::<Pubkey>().map_err(display_to_jsvalue)
1119 } else if let Some(uint8_array) = value.dyn_ref::<Uint8Array>() {
1120 Pubkey::try_from(uint8_array.to_vec())
1121 .map_err(|err| JsValue::from(std::format!("Invalid Uint8Array pubkey: {err:?}")))
1122 } else if let Some(array) = value.dyn_ref::<Array>() {
1123 let mut bytes = std::vec![];
1124 let iterator = js_sys::try_iter(&array.values())?.expect("array to be iterable");
1125 for x in iterator {
1126 let x = x?;
1127
1128 if let Some(n) = x.as_f64() {
1129 if n >= 0. && n <= 255. {
1130 bytes.push(n as u8);
1131 continue;
1132 }
1133 }
1134 return Err(std::format!("Invalid array argument: {:?}", x).into());
1135 }
1136 Pubkey::try_from(bytes)
1137 .map_err(|err| JsValue::from(std::format!("Invalid Array pubkey: {err:?}")))
1138 } else if value.is_undefined() {
1139 Ok(Pubkey::default())
1140 } else {
1141 Err("Unsupported argument".into())
1142 }
1143 }
1144
1145 pub fn toString(&self) -> std::string::String {
1147 std::string::ToString::to_string(self)
1148 }
1149
1150 #[cfg(feature = "curve25519")]
1152 pub fn isOnCurve(&self) -> bool {
1153 self.is_on_curve()
1154 }
1155
1156 pub fn equals(&self, other: &Pubkey) -> bool {
1158 self == other
1159 }
1160
1161 pub fn toBytes(&self) -> std::boxed::Box<[u8]> {
1163 self.0.clone().into()
1164 }
1165
1166 #[cfg(feature = "sha2")]
1168 pub fn createWithSeed(base: &Pubkey, seed: &str, owner: &Pubkey) -> Result<Pubkey, JsValue> {
1169 Pubkey::create_with_seed(base, seed, owner).map_err(display_to_jsvalue)
1170 }
1171
1172 #[cfg(feature = "curve25519")]
1174 pub fn createProgramAddress(
1175 seeds: std::boxed::Box<[JsValue]>,
1176 program_id: &Pubkey,
1177 ) -> Result<Pubkey, JsValue> {
1178 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1179 let seeds_slice = seeds_vec
1180 .iter()
1181 .map(|seed| seed.as_slice())
1182 .collect::<Vec<_>>();
1183
1184 Pubkey::create_program_address(seeds_slice.as_slice(), program_id)
1185 .map_err(display_to_jsvalue)
1186 }
1187
1188 #[cfg(feature = "curve25519")]
1193 pub fn findProgramAddress(
1194 seeds: std::boxed::Box<[JsValue]>,
1195 program_id: &Pubkey,
1196 ) -> Result<JsValue, JsValue> {
1197 let seeds_vec = js_value_to_seeds_vec(&seeds)?;
1198 let seeds_slice = seeds_vec
1199 .iter()
1200 .map(|seed| seed.as_slice())
1201 .collect::<Vec<_>>();
1202
1203 let (address, bump_seed) = Pubkey::find_program_address(seeds_slice.as_slice(), program_id);
1204
1205 let result = Array::new_with_length(2);
1206 result.set(0, address.into());
1207 result.set(1, bump_seed.into());
1208 Ok(result.into())
1209 }
1210}
1211
1212#[macro_export]
1234macro_rules! declare_id {
1235 ($address:expr) => {
1236 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1238
1239 pub fn check_id(id: &$crate::Pubkey) -> bool {
1243 id == &ID
1244 }
1245
1246 pub const fn id() -> $crate::Pubkey {
1248 ID
1249 }
1250
1251 #[cfg(test)]
1252 #[test]
1253 fn test_id() {
1254 assert!(check_id(&id()));
1255 }
1256 };
1257}
1258
1259#[macro_export]
1261macro_rules! declare_deprecated_id {
1262 ($address:expr) => {
1263 pub const ID: $crate::Pubkey = $crate::Pubkey::from_str_const($address);
1265
1266 #[deprecated()]
1270 pub fn check_id(id: &$crate::Pubkey) -> bool {
1271 id == &ID
1272 }
1273
1274 #[deprecated()]
1276 pub const fn id() -> $crate::Pubkey {
1277 ID
1278 }
1279
1280 #[cfg(test)]
1281 #[test]
1282 #[allow(deprecated)]
1283 fn test_id() {
1284 assert!(check_id(&id()));
1285 }
1286 };
1287}
1288
1289#[macro_export]
1305macro_rules! pubkey {
1306 ($input:literal) => {
1307 $crate::Pubkey::from_str_const($input)
1308 };
1309}
1310
1311#[cfg(all(feature = "rand", not(target_os = "solana")))]
1313pub fn new_rand() -> Pubkey {
1314 Pubkey::from(rand::random::<[u8; PUBKEY_BYTES]>())
1315}
1316
1317#[cfg(test)]
1318mod tests {
1319 use {super::*, core::str::from_utf8, strum::IntoEnumIterator};
1320
1321 #[test]
1322 fn test_new_unique() {
1323 assert!(Pubkey::new_unique() != Pubkey::new_unique());
1324 }
1325
1326 #[test]
1327 fn pubkey_fromstr() {
1328 let pubkey = Pubkey::new_unique();
1329 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1330
1331 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1332
1333 pubkey_base58_str.push_str(&bs58::encode(pubkey.0).into_string());
1334 assert_eq!(
1335 pubkey_base58_str.parse::<Pubkey>(),
1336 Err(ParsePubkeyError::WrongSize)
1337 );
1338
1339 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1340 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1341
1342 pubkey_base58_str.truncate(pubkey_base58_str.len() / 2);
1343 assert_eq!(
1344 pubkey_base58_str.parse::<Pubkey>(),
1345 Err(ParsePubkeyError::WrongSize)
1346 );
1347
1348 let mut pubkey_base58_str = bs58::encode(pubkey.0).into_string();
1349 assert_eq!(pubkey_base58_str.parse::<Pubkey>(), Ok(pubkey));
1350
1351 pubkey_base58_str.replace_range(..1, "I");
1353 assert_eq!(
1354 pubkey_base58_str.parse::<Pubkey>(),
1355 Err(ParsePubkeyError::Invalid)
1356 );
1357
1358 let mut too_long = bs58::encode(&[255u8; PUBKEY_BYTES]).into_string();
1361 too_long.push('1');
1363 assert_eq!(too_long.parse::<Pubkey>(), Err(ParsePubkeyError::WrongSize));
1364 }
1365
1366 #[test]
1367 fn test_create_with_seed() {
1368 assert!(
1369 Pubkey::create_with_seed(&Pubkey::new_unique(), "☉", &Pubkey::new_unique()).is_ok()
1370 );
1371 assert_eq!(
1372 Pubkey::create_with_seed(
1373 &Pubkey::new_unique(),
1374 from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
1375 &Pubkey::new_unique()
1376 ),
1377 Err(PubkeyError::MaxSeedLengthExceeded)
1378 );
1379 assert!(Pubkey::create_with_seed(
1380 &Pubkey::new_unique(),
1381 "\
1382 \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1383 ",
1384 &Pubkey::new_unique()
1385 )
1386 .is_ok());
1387 assert_eq!(
1389 Pubkey::create_with_seed(
1390 &Pubkey::new_unique(),
1391 "\
1392 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
1393 ",
1394 &Pubkey::new_unique()
1395 ),
1396 Err(PubkeyError::MaxSeedLengthExceeded)
1397 );
1398
1399 assert!(Pubkey::create_with_seed(
1400 &Pubkey::new_unique(),
1401 from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
1402 &Pubkey::new_unique(),
1403 )
1404 .is_ok());
1405
1406 assert!(
1407 Pubkey::create_with_seed(&Pubkey::new_unique(), "", &Pubkey::new_unique(),).is_ok()
1408 );
1409
1410 assert_eq!(
1411 Pubkey::create_with_seed(
1412 &Pubkey::default(),
1413 "limber chicken: 4/45",
1414 &Pubkey::default(),
1415 ),
1416 Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
1417 .parse()
1418 .unwrap())
1419 );
1420 }
1421
1422 #[test]
1423 fn test_create_program_address() {
1424 let exceeded_seed = &[127; MAX_SEED_LEN + 1];
1425 let max_seed = &[0; MAX_SEED_LEN];
1426 let exceeded_seeds: &[&[u8]] = &[
1427 &[1],
1428 &[2],
1429 &[3],
1430 &[4],
1431 &[5],
1432 &[6],
1433 &[7],
1434 &[8],
1435 &[9],
1436 &[10],
1437 &[11],
1438 &[12],
1439 &[13],
1440 &[14],
1441 &[15],
1442 &[16],
1443 &[17],
1444 ];
1445 let max_seeds: &[&[u8]] = &[
1446 &[1],
1447 &[2],
1448 &[3],
1449 &[4],
1450 &[5],
1451 &[6],
1452 &[7],
1453 &[8],
1454 &[9],
1455 &[10],
1456 &[11],
1457 &[12],
1458 &[13],
1459 &[14],
1460 &[15],
1461 &[16],
1462 ];
1463 let program_id = Pubkey::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
1464 let public_key = Pubkey::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
1465
1466 assert_eq!(
1467 Pubkey::create_program_address(&[exceeded_seed], &program_id),
1468 Err(PubkeyError::MaxSeedLengthExceeded)
1469 );
1470 assert_eq!(
1471 Pubkey::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
1472 Err(PubkeyError::MaxSeedLengthExceeded)
1473 );
1474 assert!(Pubkey::create_program_address(&[max_seed], &program_id).is_ok());
1475 assert_eq!(
1476 Pubkey::create_program_address(exceeded_seeds, &program_id),
1477 Err(PubkeyError::MaxSeedLengthExceeded)
1478 );
1479 assert!(Pubkey::create_program_address(max_seeds, &program_id).is_ok());
1480 assert_eq!(
1481 Pubkey::create_program_address(&[b"", &[1]], &program_id),
1482 Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
1483 .parse()
1484 .unwrap())
1485 );
1486 assert_eq!(
1487 Pubkey::create_program_address(&["☉".as_ref(), &[0]], &program_id),
1488 Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
1489 .parse()
1490 .unwrap())
1491 );
1492 assert_eq!(
1493 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
1494 Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
1495 .parse()
1496 .unwrap())
1497 );
1498 assert_eq!(
1499 Pubkey::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
1500 Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
1501 .parse()
1502 .unwrap())
1503 );
1504 assert_ne!(
1505 Pubkey::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
1506 Pubkey::create_program_address(&[b"Talking"], &program_id).unwrap(),
1507 );
1508 }
1509
1510 #[test]
1511 fn test_pubkey_off_curve() {
1512 let mut addresses = std::vec![];
1515 for _ in 0..1_000 {
1516 let program_id = Pubkey::new_unique();
1517 let bytes1 = rand::random::<[u8; 10]>();
1518 let bytes2 = rand::random::<[u8; 32]>();
1519 if let Ok(program_address) =
1520 Pubkey::create_program_address(&[&bytes1, &bytes2], &program_id)
1521 {
1522 assert!(!program_address.is_on_curve());
1523 assert!(!addresses.contains(&program_address));
1524 addresses.push(program_address);
1525 }
1526 }
1527 }
1528
1529 #[test]
1530 fn test_find_program_address() {
1531 for _ in 0..1_000 {
1532 let program_id = Pubkey::new_unique();
1533 let (address, bump_seed) =
1534 Pubkey::find_program_address(&[b"Lil'", b"Bits"], &program_id);
1535 assert_eq!(
1536 address,
1537 Pubkey::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
1538 .unwrap()
1539 );
1540 }
1541 }
1542
1543 fn pubkey_from_seed_by_marker(marker: &[u8]) -> Result<Pubkey, PubkeyError> {
1544 let key = Pubkey::new_unique();
1545 let owner = Pubkey::default();
1546
1547 let mut to_fake = owner.to_bytes().to_vec();
1548 to_fake.extend_from_slice(marker);
1549
1550 let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
1551 let base = &Pubkey::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
1552
1553 Pubkey::create_with_seed(&key, seed, base)
1554 }
1555
1556 #[test]
1557 fn test_create_with_seed_rejects_illegal_owner() {
1558 assert_eq!(
1559 pubkey_from_seed_by_marker(PDA_MARKER),
1560 Err(PubkeyError::IllegalOwner)
1561 );
1562 assert!(pubkey_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
1563 }
1564
1565 #[test]
1566 fn test_pubkey_error_from_primitive_exhaustive() {
1567 for variant in PubkeyError::iter() {
1568 let variant_i64 = variant.clone() as i64;
1569 assert_eq!(
1570 PubkeyError::from_repr(variant_i64 as usize),
1571 PubkeyError::from_i64(variant_i64)
1572 );
1573 assert_eq!(PubkeyError::from(variant_i64 as u64), variant);
1574 }
1575 }
1576
1577 #[test]
1578 fn test_parse_pubkey_error_from_primitive_exhaustive() {
1579 for variant in ParsePubkeyError::iter() {
1580 let variant_i64 = variant as i64;
1581 assert_eq!(
1582 ParsePubkeyError::from_repr(variant_i64 as usize),
1583 ParsePubkeyError::from_i64(variant_i64)
1584 );
1585 }
1586 }
1587
1588 #[test]
1589 fn test_pubkey_macro() {
1590 const PK: Pubkey = Pubkey::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
1591 assert_eq!(pubkey!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"), PK);
1592 assert_eq!(
1593 Pubkey::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
1594 PK
1595 );
1596 }
1597
1598 #[test]
1599 fn test_as_array() {
1600 let bytes = [1u8; 32];
1601 let key = Pubkey::from(bytes);
1602 assert_eq!(key.as_array(), &bytes);
1603 assert_eq!(key.as_array(), &key.to_bytes());
1604 assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
1606 }
1607}