atlas_address/
lib.rs

1//! Address representation for Atlas.
2//!
3//! An address is a sequence of 32 bytes, often shown as a base58 encoded string
4//! (e.g. 14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5).
5
6#![no_std]
7#![cfg_attr(docsrs, feature(doc_auto_cfg))]
8#![cfg_attr(feature = "frozen-abi", feature(min_specialization))]
9#![allow(clippy::arithmetic_side_effects)]
10
11#[cfg(feature = "error")]
12pub mod error;
13#[cfg(feature = "rand")]
14mod hasher;
15#[cfg(any(feature = "curve25519", feature = "syscalls"))]
16pub mod syscalls;
17
18#[cfg(feature = "sha2")]
19use crate::error::AddressError;
20#[cfg(feature = "decode")]
21use crate::error::ParseAddressError;
22#[cfg(all(feature = "rand", not(target_os = "atlas")))]
23pub use crate::hasher::{AddressHasher, AddressHasherBuilder};
24
25#[cfg(feature = "std")]
26extern crate std;
27#[cfg(feature = "dev-context-only-utils")]
28use arbitrary::Arbitrary;
29#[cfg(feature = "bytemuck")]
30use bytemuck_derive::{Pod, Zeroable};
31#[cfg(feature = "decode")]
32use core::str::FromStr;
33use core::{
34    array,
35    convert::TryFrom,
36    hash::{Hash, Hasher},
37};
38#[cfg(feature = "serde")]
39use serde_derive::{Deserialize, Serialize};
40#[cfg(feature = "std")]
41use std::vec::Vec;
42#[cfg(feature = "borsh")]
43use {
44    borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
45    std::string::ToString,
46};
47
48/// Number of bytes in an address.
49pub const ADDRESS_BYTES: usize = 32;
50/// maximum length of derived `Address` seed
51pub const MAX_SEED_LEN: usize = 32;
52/// Maximum number of seeds
53pub const MAX_SEEDS: usize = 16;
54#[cfg(feature = "decode")]
55/// Maximum string length of a base58 encoded address.
56const MAX_BASE58_LEN: usize = 44;
57
58#[cfg(feature = "sha2")]
59const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
60
61/// The address of a [Atlas account][acc].
62///
63/// Some account addresses are [ed25519] public keys, with corresponding secret
64/// keys that are managed off-chain. Often, though, account addresses do not
65/// have corresponding secret keys — as with [_program derived
66/// addresses_][pdas] — or the secret key is not relevant to the operation
67/// of a program, and may have even been disposed of. As running Atlas programs
68/// can not safely create or manage secret keys, the full [`Keypair`] is not
69/// defined in `atlas-program` but in `atlas-sdk`.
70///
71/// [acc]: https://atlaschain.org/docs/core/accounts
72/// [ed25519]: https://ed25519.cr.yp.to/
73/// [pdas]: https://atlaschain.org/docs/core/cpi#program-derived-addresses
74/// [`Keypair`]: https://docs.rs/atlas-sdk/latest/atlas_sdk/signer/keypair/struct.Keypair.html
75#[repr(transparent)]
76#[cfg_attr(feature = "frozen-abi", derive(atlas_frozen_abi_macro::AbiExample))]
77#[cfg_attr(
78    feature = "borsh",
79    derive(BorshSerialize, BorshDeserialize),
80    borsh(crate = "borsh")
81)]
82#[cfg_attr(all(feature = "borsh", feature = "std"), derive(BorshSchema))]
83#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
84#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
85#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
86#[cfg_attr(feature = "dev-context-only-utils", derive(Arbitrary))]
87pub struct Address(pub(crate) [u8; 32]);
88
89#[cfg(feature = "sanitize")]
90impl atlas_sanitize::Sanitize for Address {}
91
92#[cfg(feature = "decode")]
93impl FromStr for Address {
94    type Err = ParseAddressError;
95
96    fn from_str(s: &str) -> Result<Self, Self::Err> {
97        use five8::DecodeError;
98        if s.len() > MAX_BASE58_LEN {
99            return Err(ParseAddressError::WrongSize);
100        }
101        let mut bytes = [0; ADDRESS_BYTES];
102        five8::decode_32(s, &mut bytes).map_err(|e| match e {
103            DecodeError::InvalidChar(_) => ParseAddressError::Invalid,
104            DecodeError::TooLong
105            | DecodeError::TooShort
106            | DecodeError::LargestTermTooHigh
107            | DecodeError::OutputTooLong => ParseAddressError::WrongSize,
108        })?;
109        Ok(Address(bytes))
110    }
111}
112
113/// Custom impl of Hash for Address.
114///
115/// This allows us to skip hashing the length of the address
116/// which is always the same anyway.
117impl Hash for Address {
118    fn hash<H: Hasher>(&self, state: &mut H) {
119        state.write(self.as_array());
120    }
121}
122
123impl From<&Address> for Address {
124    #[inline]
125    fn from(value: &Address) -> Self {
126        *value
127    }
128}
129
130impl From<[u8; 32]> for Address {
131    #[inline]
132    fn from(from: [u8; 32]) -> Self {
133        Self(from)
134    }
135}
136
137impl TryFrom<&[u8]> for Address {
138    type Error = array::TryFromSliceError;
139
140    #[inline]
141    fn try_from(address: &[u8]) -> Result<Self, Self::Error> {
142        <[u8; 32]>::try_from(address).map(Self::from)
143    }
144}
145
146#[cfg(feature = "std")]
147impl TryFrom<Vec<u8>> for Address {
148    type Error = Vec<u8>;
149
150    #[inline]
151    fn try_from(address: Vec<u8>) -> Result<Self, Self::Error> {
152        <[u8; 32]>::try_from(address).map(Self::from)
153    }
154}
155#[cfg(feature = "decode")]
156impl TryFrom<&str> for Address {
157    type Error = ParseAddressError;
158    fn try_from(s: &str) -> Result<Self, Self::Error> {
159        Address::from_str(s)
160    }
161}
162
163// If target_os = "atlas", then this panics so there are no dependencies.
164// When target_os != "atlas", this should be opt-in so users
165// don't need the curve25519 dependency.
166#[cfg(any(target_os = "atlas", feature = "curve25519"))]
167#[allow(clippy::used_underscore_binding)]
168pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
169    #[cfg(not(target_os = "atlas"))]
170    {
171        let Ok(compressed_edwards_y) =
172            curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
173        else {
174            return false;
175        };
176        compressed_edwards_y.decompress().is_some()
177    }
178    #[cfg(target_os = "atlas")]
179    unimplemented!();
180}
181
182impl Address {
183    pub const fn new_from_array(address_array: [u8; 32]) -> Self {
184        Self(address_array)
185    }
186
187    #[cfg(feature = "decode")]
188    /// Decode a string into an `Address`, usable in a const context
189    pub const fn from_str_const(s: &str) -> Self {
190        let id_array = five8_const::decode_32_const(s);
191        Address::new_from_array(id_array)
192    }
193
194    #[cfg(feature = "atomic")]
195    /// Create an unique `Address` for tests and benchmarks.
196    pub fn new_unique() -> Self {
197        use atlas_atomic_u64::AtomicU64;
198        static I: AtomicU64 = AtomicU64::new(1);
199        type T = u32;
200        const COUNTER_BYTES: usize = core::mem::size_of::<T>();
201        let mut b = [0u8; ADDRESS_BYTES];
202        #[cfg(feature = "std")]
203        let mut i = I.fetch_add(1) as T;
204        #[cfg(not(feature = "std"))]
205        let i = I.fetch_add(1) as T;
206        // use big endian representation to ensure that recent unique addresses
207        // are always greater than less recent unique addresses.
208        b[0..COUNTER_BYTES].copy_from_slice(&i.to_be_bytes());
209        // fill the rest of the address with pseudorandom numbers to make
210        // data statistically similar to real addresses.
211        #[cfg(feature = "std")]
212        {
213            let mut hash = std::hash::DefaultHasher::new();
214            for slice in b[COUNTER_BYTES..].chunks_mut(COUNTER_BYTES) {
215                hash.write_u32(i);
216                i += 1;
217                slice.copy_from_slice(&hash.finish().to_ne_bytes()[0..COUNTER_BYTES]);
218            }
219        }
220        // if std is not available, just replicate last byte of the counter.
221        // this is not as good as a proper hash, but at least it is uniform
222        #[cfg(not(feature = "std"))]
223        {
224            for b in b[COUNTER_BYTES..].iter_mut() {
225                *b = (i & 0xFF) as u8;
226            }
227        }
228        Self::from(b)
229    }
230
231    // If target_os = "atlas", then the atlas_sha256_hasher crate will use
232    // syscalls which bring no dependencies.
233    // When target_os != "atlas", this should be opt-in so users
234    // don't need the sha2 dependency.
235    #[cfg(feature = "sha2")]
236    pub fn create_with_seed(
237        base: &Address,
238        seed: &str,
239        owner: &Address,
240    ) -> Result<Address, AddressError> {
241        if seed.len() > MAX_SEED_LEN {
242            return Err(AddressError::MaxSeedLengthExceeded);
243        }
244
245        let owner = owner.as_ref();
246        if owner.len() >= PDA_MARKER.len() {
247            let slice = &owner[owner.len() - PDA_MARKER.len()..];
248            if slice == PDA_MARKER {
249                return Err(AddressError::IllegalOwner);
250            }
251        }
252        let hash = atlas_sha256_hasher::hashv(&[base.as_ref(), seed.as_ref(), owner]);
253        Ok(Address::from(hash.to_bytes()))
254    }
255
256    pub const fn to_bytes(self) -> [u8; 32] {
257        self.0
258    }
259
260    /// Return a reference to the `Address`'s byte array.
261    #[inline(always)]
262    pub const fn as_array(&self) -> &[u8; 32] {
263        &self.0
264    }
265
266    // If target_os = "atlas", then this panics so there are no dependencies.
267    // When target_os != "atlas", this should be opt-in so users
268    // don't need the curve25519 dependency.
269    #[cfg(any(target_os = "atlas", feature = "curve25519"))]
270    pub fn is_on_curve(&self) -> bool {
271        bytes_are_curve_point(self)
272    }
273}
274
275impl AsRef<[u8]> for Address {
276    fn as_ref(&self) -> &[u8] {
277        &self.0[..]
278    }
279}
280
281impl AsMut<[u8]> for Address {
282    fn as_mut(&mut self) -> &mut [u8] {
283        &mut self.0[..]
284    }
285}
286
287#[cfg(feature = "decode")]
288fn write_as_base58(f: &mut core::fmt::Formatter, p: &Address) -> core::fmt::Result {
289    let mut out = [0u8; MAX_BASE58_LEN];
290    let len = five8::encode_32(&p.0, &mut out) as usize;
291    // any sequence of base58 chars is valid utf8
292    let as_str = unsafe { core::str::from_utf8_unchecked(&out[..len]) };
293    f.write_str(as_str)
294}
295
296#[cfg(feature = "decode")]
297impl core::fmt::Debug for Address {
298    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
299        write_as_base58(f, self)
300    }
301}
302
303#[cfg(feature = "decode")]
304impl core::fmt::Display for Address {
305    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
306        write_as_base58(f, self)
307    }
308}
309
310/// Convenience macro to define a static `Address` value.
311///
312/// Input: a single literal base58 string representation of an `Address`.
313///
314/// # Example
315///
316/// ```
317/// use std::str::FromStr;
318/// use atlas_address::{address, Address};
319///
320/// static ID: Address = address!("My11111111111111111111111111111111111111111");
321///
322/// let my_id = Address::from_str("My11111111111111111111111111111111111111111").unwrap();
323/// assert_eq!(ID, my_id);
324/// ```
325#[macro_export]
326macro_rules! address {
327    ($input:literal) => {
328        $crate::Address::from_str_const($input)
329    };
330}
331
332#[cfg(test)]
333mod tests {
334    use {super::*, core::str::from_utf8, std::string::String};
335
336    fn encode_address(address: &[u8; 32]) -> String {
337        let mut buffer = [0u8; 44];
338        let count = five8::encode_32(address, &mut buffer);
339        from_utf8(&buffer[..count as usize]).unwrap().to_string()
340    }
341
342    #[test]
343    fn test_new_unique() {
344        assert!(Address::new_unique() != Address::new_unique());
345    }
346
347    #[test]
348    fn address_fromstr() {
349        let address = Address::new_unique();
350        let mut address_base58_str = encode_address(&address.0);
351
352        assert_eq!(address_base58_str.parse::<Address>(), Ok(address));
353
354        address_base58_str.push_str(&encode_address(&address.0));
355        assert_eq!(
356            address_base58_str.parse::<Address>(),
357            Err(ParseAddressError::WrongSize)
358        );
359
360        address_base58_str.truncate(address_base58_str.len() / 2);
361        assert_eq!(address_base58_str.parse::<Address>(), Ok(address));
362
363        address_base58_str.truncate(address_base58_str.len() / 2);
364        assert_eq!(
365            address_base58_str.parse::<Address>(),
366            Err(ParseAddressError::WrongSize)
367        );
368
369        let mut address_base58_str = encode_address(&address.0);
370        assert_eq!(address_base58_str.parse::<Address>(), Ok(address));
371
372        // throw some non-base58 stuff in there
373        address_base58_str.replace_range(..1, "I");
374        assert_eq!(
375            address_base58_str.parse::<Address>(),
376            Err(ParseAddressError::Invalid)
377        );
378
379        // too long input string
380        // longest valid encoding
381        let mut too_long = encode_address(&[255u8; ADDRESS_BYTES]);
382        // and one to grow on
383        too_long.push('1');
384        assert_eq!(
385            too_long.parse::<Address>(),
386            Err(ParseAddressError::WrongSize)
387        );
388    }
389
390    #[test]
391    fn test_create_with_seed() {
392        assert!(
393            Address::create_with_seed(&Address::new_unique(), "☉", &Address::new_unique()).is_ok()
394        );
395        assert_eq!(
396            Address::create_with_seed(
397                &Address::new_unique(),
398                from_utf8(&[127; MAX_SEED_LEN + 1]).unwrap(),
399                &Address::new_unique()
400            ),
401            Err(AddressError::MaxSeedLengthExceeded)
402        );
403        assert!(Address::create_with_seed(
404            &Address::new_unique(),
405            "\
406             \u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
407             ",
408            &Address::new_unique()
409        )
410        .is_ok());
411        // utf-8 abuse ;)
412        assert_eq!(
413            Address::create_with_seed(
414                &Address::new_unique(),
415                "\
416                 x\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\u{10FFFF}\
417                 ",
418                &Address::new_unique()
419            ),
420            Err(AddressError::MaxSeedLengthExceeded)
421        );
422
423        assert!(Address::create_with_seed(
424            &Address::new_unique(),
425            from_utf8(&[0; MAX_SEED_LEN]).unwrap(),
426            &Address::new_unique(),
427        )
428        .is_ok());
429
430        assert!(
431            Address::create_with_seed(&Address::new_unique(), "", &Address::new_unique(),).is_ok()
432        );
433
434        assert_eq!(
435            Address::create_with_seed(
436                &Address::default(),
437                "limber chicken: 4/45",
438                &Address::default(),
439            ),
440            Ok("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"
441                .parse()
442                .unwrap())
443        );
444    }
445
446    #[test]
447    fn test_create_program_address() {
448        let exceeded_seed = &[127; MAX_SEED_LEN + 1];
449        let max_seed = &[0; MAX_SEED_LEN];
450        let exceeded_seeds: &[&[u8]] = &[
451            &[1],
452            &[2],
453            &[3],
454            &[4],
455            &[5],
456            &[6],
457            &[7],
458            &[8],
459            &[9],
460            &[10],
461            &[11],
462            &[12],
463            &[13],
464            &[14],
465            &[15],
466            &[16],
467            &[17],
468        ];
469        let max_seeds: &[&[u8]] = &[
470            &[1],
471            &[2],
472            &[3],
473            &[4],
474            &[5],
475            &[6],
476            &[7],
477            &[8],
478            &[9],
479            &[10],
480            &[11],
481            &[12],
482            &[13],
483            &[14],
484            &[15],
485            &[16],
486        ];
487        let program_id = Address::from_str("BPFLoaderUpgradeab1e11111111111111111111111").unwrap();
488        let public_key = Address::from_str("SeedPubey1111111111111111111111111111111111").unwrap();
489
490        assert_eq!(
491            Address::create_program_address(&[exceeded_seed], &program_id),
492            Err(AddressError::MaxSeedLengthExceeded)
493        );
494        assert_eq!(
495            Address::create_program_address(&[b"short_seed", exceeded_seed], &program_id),
496            Err(AddressError::MaxSeedLengthExceeded)
497        );
498        assert!(Address::create_program_address(&[max_seed], &program_id).is_ok());
499        assert_eq!(
500            Address::create_program_address(exceeded_seeds, &program_id),
501            Err(AddressError::MaxSeedLengthExceeded)
502        );
503        assert!(Address::create_program_address(max_seeds, &program_id).is_ok());
504        assert_eq!(
505            Address::create_program_address(&[b"", &[1]], &program_id),
506            Ok("BwqrghZA2htAcqq8dzP1WDAhTXYTYWj7CHxF5j7TDBAe"
507                .parse()
508                .unwrap())
509        );
510        assert_eq!(
511            Address::create_program_address(&["☉".as_ref(), &[0]], &program_id),
512            Ok("13yWmRpaTR4r5nAktwLqMpRNr28tnVUZw26rTvPSSB19"
513                .parse()
514                .unwrap())
515        );
516        assert_eq!(
517            Address::create_program_address(&[b"Talking", b"Squirrels"], &program_id),
518            Ok("2fnQrngrQT4SeLcdToJAD96phoEjNL2man2kfRLCASVk"
519                .parse()
520                .unwrap())
521        );
522        assert_eq!(
523            Address::create_program_address(&[public_key.as_ref(), &[1]], &program_id),
524            Ok("976ymqVnfE32QFe6NfGDctSvVa36LWnvYxhU6G2232YL"
525                .parse()
526                .unwrap())
527        );
528        assert_ne!(
529            Address::create_program_address(&[b"Talking", b"Squirrels"], &program_id).unwrap(),
530            Address::create_program_address(&[b"Talking"], &program_id).unwrap(),
531        );
532    }
533
534    #[test]
535    fn test_address_off_curve() {
536        // try a bunch of random input, all successful generated program
537        // addresses must land off the curve and be unique
538        let mut addresses = std::vec![];
539        for _ in 0..1_000 {
540            let program_id = Address::new_unique();
541            let bytes1 = rand::random::<[u8; 10]>();
542            let bytes2 = rand::random::<[u8; 32]>();
543            if let Ok(program_address) =
544                Address::create_program_address(&[&bytes1, &bytes2], &program_id)
545            {
546                assert!(!program_address.is_on_curve());
547                assert!(!addresses.contains(&program_address));
548                addresses.push(program_address);
549            }
550        }
551    }
552
553    #[test]
554    fn test_find_program_address() {
555        for _ in 0..1_000 {
556            let program_id = Address::new_unique();
557            let (address, bump_seed) =
558                Address::find_program_address(&[b"Lil'", b"Bits"], &program_id);
559            assert_eq!(
560                address,
561                Address::create_program_address(&[b"Lil'", b"Bits", &[bump_seed]], &program_id)
562                    .unwrap()
563            );
564        }
565    }
566
567    fn address_from_seed_by_marker(marker: &[u8]) -> Result<Address, AddressError> {
568        let key = Address::new_unique();
569        let owner = Address::default();
570
571        let mut to_fake = owner.to_bytes().to_vec();
572        to_fake.extend_from_slice(marker);
573
574        let seed = from_utf8(&to_fake[..to_fake.len() - 32]).expect("not utf8");
575        let base = &Address::try_from(&to_fake[to_fake.len() - 32..]).unwrap();
576
577        Address::create_with_seed(&key, seed, base)
578    }
579
580    #[test]
581    fn test_create_with_seed_rejects_illegal_owner() {
582        assert_eq!(
583            address_from_seed_by_marker(PDA_MARKER),
584            Err(AddressError::IllegalOwner)
585        );
586        assert!(address_from_seed_by_marker(&PDA_MARKER[1..]).is_ok());
587    }
588
589    #[test]
590    fn test_as_array() {
591        let bytes = [1u8; 32];
592        let key = Address::from(bytes);
593        assert_eq!(key.as_array(), &bytes);
594        assert_eq!(key.as_array(), &key.to_bytes());
595        // Sanity check: ensure the pointer is the same.
596        assert_eq!(key.as_array().as_ptr(), key.0.as_ptr());
597    }
598
599    #[test]
600    fn test_address_macro() {
601        const ADDRESS: Address =
602            Address::from_str_const("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq");
603        assert_eq!(
604            address!("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq"),
605            ADDRESS
606        );
607        assert_eq!(
608            Address::from_str("9h1HyLCW5dZnBVap8C5egQ9Z6pHyjsh5MNy83iPqqRuq").unwrap(),
609            ADDRESS
610        );
611    }
612}