Skip to main content

hopper_native/
address.rs

1//! Solana address type -- 32-byte public key.
2
3/// Number of bytes in an address.
4pub const ADDRESS_BYTES: usize = 32;
5
6/// Maximum length of a single PDA seed.
7pub const MAX_SEED_LEN: usize = 32;
8
9/// Maximum number of seeds for PDA derivation.
10pub const MAX_SEEDS: usize = 16;
11
12/// Marker appended to PDA hash inputs: `"ProgramDerivedAddress"`.
13pub const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
14
15/// A Solana address (public key): 32 bytes, transparent layout.
16///
17/// `PartialEq`/`Eq` are implemented manually (see below) so every
18/// `Address == Address` -- and every owner / program-id check that
19/// funnels through [`address_eq`] -- compiles to a 4 x u64 word
20/// compare instead of a bytewise loop. `PartialOrd`/`Ord` stay
21/// derived: word-equality and byte-equality decide the same pairs
22/// equal, so the derived ordering remains consistent with the manual
23/// equality.
24#[repr(transparent)]
25#[cfg_attr(feature = "copy", derive(Copy))]
26#[derive(Clone, Default, Ord, PartialOrd)]
27pub struct Address(pub(crate) [u8; 32]);
28
29impl PartialEq for Address {
30    /// Word-compare equality: delegates to [`address_eq`] so the
31    /// `==` operator is exactly as fast as the free function.
32    #[inline(always)]
33    fn eq(&self, other: &Self) -> bool {
34        address_eq(self, other)
35    }
36}
37
38// Word-equality is an equivalence relation: it decides equal exactly
39// when all 32 bytes match, same as the previously-derived impl.
40impl Eq for Address {}
41
42impl Address {
43    /// Construct from a raw byte array.
44    #[inline(always)]
45    pub const fn new_from_array(bytes: [u8; 32]) -> Self {
46        Self(bytes)
47    }
48
49    /// Return the underlying bytes by value.
50    #[inline(always)]
51    pub const fn to_bytes(&self) -> [u8; 32] {
52        self.0
53    }
54
55    /// Borrow the underlying byte array.
56    #[inline(always)]
57    pub const fn as_array(&self) -> &[u8; 32] {
58        &self.0
59    }
60}
61
62impl From<[u8; 32]> for Address {
63    #[inline(always)]
64    fn from(bytes: [u8; 32]) -> Self {
65        Self(bytes)
66    }
67}
68
69impl From<Address> for [u8; 32] {
70    #[inline(always)]
71    fn from(addr: Address) -> [u8; 32] {
72        addr.0
73    }
74}
75
76impl TryFrom<&[u8]> for Address {
77    type Error = core::array::TryFromSliceError;
78
79    #[inline]
80    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
81        let arr: [u8; 32] = slice.try_into()?;
82        Ok(Self(arr))
83    }
84}
85
86impl AsRef<[u8]> for Address {
87    #[inline(always)]
88    fn as_ref(&self) -> &[u8] {
89        &self.0
90    }
91}
92
93impl AsMut<[u8]> for Address {
94    #[inline(always)]
95    fn as_mut(&mut self) -> &mut [u8] {
96        &mut self.0
97    }
98}
99
100impl AsRef<[u8; 32]> for Address {
101    #[inline(always)]
102    fn as_ref(&self) -> &[u8; 32] {
103        &self.0
104    }
105}
106
107impl core::hash::Hash for Address {
108    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
109        self.0.hash(state);
110    }
111}
112
113impl core::fmt::Debug for Address {
114    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115        write!(f, "Address({:?})", &self.0[..4])
116    }
117}
118
119/// Decode a base58 Solana address literal into 32 bytes at compile time.
120pub const fn decode_base58_32(input: &str) -> [u8; 32] {
121    let bytes = input.as_bytes();
122    let mut out = [0u8; ADDRESS_BYTES];
123    let mut i = 0;
124
125    while i < bytes.len() {
126        let mut carry = base58_digit(bytes[i]) as u32;
127        let mut j = ADDRESS_BYTES;
128
129        while j > 0 {
130            j -= 1;
131            let value = (out[j] as u32) * 58 + carry;
132            out[j] = value as u8;
133            carry = value >> 8;
134        }
135
136        if carry != 0 {
137            panic!("base58 address literal overflows 32 bytes");
138        }
139
140        i += 1;
141    }
142
143    out
144}
145
146const fn base58_digit(byte: u8) -> u8 {
147    match byte {
148        b'1'..=b'9' => byte - b'1',
149        b'A'..=b'H' => byte - b'A' + 9,
150        b'J'..=b'N' => byte - b'J' + 17,
151        b'P'..=b'Z' => byte - b'P' + 22,
152        b'a'..=b'k' => byte - b'a' + 33,
153        b'm'..=b'z' => byte - b'm' + 44,
154        _ => panic!("invalid base58 address literal"),
155    }
156}
157
158/// Address equality over raw bytes: 4 x u64 word comparison.
159///
160/// Short-circuits on the first differing 8-byte chunk. This is the
161/// single equality body behind every backend key check (owner checks,
162/// CPI account validation, instruction introspection, PDA bump
163/// search), so it must stay branch-light and inlinable.
164#[inline(always)]
165pub fn address_eq(a: &Address, b: &Address) -> bool {
166    let a_ptr = a.0.as_ptr() as *const u64;
167    let b_ptr = b.0.as_ptr() as *const u64;
168    // SAFETY: Address is #[repr(transparent)] over [u8; 32] = 4 x u64,
169    // so all four reads on each side are in bounds. Use unaligned reads
170    // because Address is only byte-aligned.
171    unsafe {
172        core::ptr::read_unaligned(a_ptr) == core::ptr::read_unaligned(b_ptr)
173            && core::ptr::read_unaligned(a_ptr.add(1)) == core::ptr::read_unaligned(b_ptr.add(1))
174            && core::ptr::read_unaligned(a_ptr.add(2)) == core::ptr::read_unaligned(b_ptr.add(2))
175            && core::ptr::read_unaligned(a_ptr.add(3)) == core::ptr::read_unaligned(b_ptr.add(3))
176    }
177}
178
179/// Compile-time base58 address literal.
180///
181/// Usage: `const MY_ADDR: Address = address!("11111111111111111111111111111111");`
182#[macro_export]
183macro_rules! address {
184    ( $literal:expr ) => {
185        $crate::address::Address::new_from_array($crate::address::decode_base58_32($literal))
186    };
187}
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192
193    #[test]
194    fn decodes_system_program_literal() {
195        const SYSTEM: [u8; 32] = decode_base58_32("11111111111111111111111111111111");
196        assert_eq!(SYSTEM, [0u8; 32]);
197    }
198
199    #[test]
200    fn address_macro_uses_local_decoder() {
201        const SYSTEM: Address = crate::address!("11111111111111111111111111111111");
202        assert_eq!(SYSTEM.to_bytes(), [0u8; 32]);
203    }
204
205    /// Edge patterns exercised by the equality tests below.
206    fn edge_patterns() -> [[u8; 32]; 6] {
207        let mut ramp = [0u8; 32];
208        let mut i = 0;
209        while i < 32 {
210            ramp[i] = i as u8;
211            i += 1;
212        }
213        let mut last_hi = [0u8; 32];
214        last_hi[31] = 0xFF;
215        let mut first_hi = [0u8; 32];
216        first_hi[0] = 0xFF;
217        [
218            [0u8; 32],
219            [0xFFu8; 32],
220            ramp,
221            last_hi,
222            first_hi,
223            [0xA5u8; 32],
224        ]
225    }
226
227    #[test]
228    fn address_eq_matches_bytewise_on_equal_arrays() {
229        for pat in edge_patterns() {
230            let a = Address::new_from_array(pat);
231            let b = Address::new_from_array(pat);
232            assert!(address_eq(&a, &b));
233            assert_eq!(a, b);
234        }
235    }
236
237    #[test]
238    fn address_eq_detects_single_byte_difference_at_every_index() {
239        for base in edge_patterns() {
240            for idx in 0..32 {
241                let mut other = base;
242                other[idx] ^= 0x01;
243                let a = Address::new_from_array(base);
244                let b = Address::new_from_array(other);
245                assert!(!address_eq(&a, &b), "missed diff at byte {idx}");
246                assert_ne!(a, b);
247                // Word compare must agree with bytewise compare.
248                assert_eq!(address_eq(&a, &b), base == other);
249            }
250        }
251    }
252
253    #[test]
254    fn address_eq_differs_only_in_last_byte() {
255        let base = [7u8; 32];
256        let mut other = base;
257        other[31] = 8;
258        let a = Address::new_from_array(base);
259        let b = Address::new_from_array(other);
260        assert!(!address_eq(&a, &b));
261        assert_ne!(a, b);
262    }
263
264    #[test]
265    fn eq_is_consistent_with_derived_ord() {
266        use core::cmp::Ordering;
267        let patterns = edge_patterns();
268        for a in patterns {
269            for b in patterns {
270                let aa = Address::new_from_array(a);
271                let ab = Address::new_from_array(b);
272                // Manual PartialEq must agree with derived Ord.
273                assert_eq!(aa == ab, aa.cmp(&ab) == Ordering::Equal);
274                // ...and with bytewise equality on the raw arrays.
275                assert_eq!(aa == ab, a == b);
276                for idx in 0..32 {
277                    let mut c = a;
278                    c[idx] = c[idx].wrapping_add(1);
279                    let ac = Address::new_from_array(c);
280                    assert_eq!(aa == ac, aa.cmp(&ac) == Ordering::Equal);
281                    assert_eq!(aa == ac, a == c);
282                }
283            }
284        }
285    }
286}