1use thruput_xdr::base58;
8use zeroize::Zeroizing;
9
10pub use base58::max_encoded_len;
11
12const ADDRESS_STRKEY_VERSION: u8 = 25 << 3;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
17pub enum Error {
18 #[error("invalid base58 encoding")]
20 InvalidBase58,
21 #[error("base58 value exceeds the {max_bytes}-byte limit")]
23 TooLong {
24 max_bytes: usize,
26 },
27 #[error("expected {expected} bytes, received {actual}")]
29 InvalidLength {
30 expected: usize,
32 actual: usize,
34 },
35 #[error("expected a 32-byte base58 value or a valid {prefix} StrKey")]
37 InvalidTypedValue {
38 prefix: char,
40 },
41}
42
43pub fn encode(bytes: &[u8]) -> String {
45 let mut output = vec![0; max_encoded_len(bytes.len())];
46 let len = base58::encode_into(bytes, &mut output)
47 .expect("bug: base58 encoded length exceeds its bound");
48 output.truncate(len);
49 String::from_utf8(output).expect("bug: base58 encoding is not ASCII")
50}
51
52fn decode_error(error: base58::Error, max_bytes: usize) -> Error {
53 match error {
54 base58::Error::BufferTooSmall => Error::TooLong { max_bytes },
55 base58::Error::InvalidLength { expected, actual } => {
56 Error::InvalidLength { expected, actual }
57 }
58 _ => Error::InvalidBase58,
59 }
60}
61
62pub fn decode(text: &str, max_bytes: usize) -> Result<Vec<u8>, Error> {
68 if text.len() > max_encoded_len(max_bytes) {
69 return Err(Error::TooLong { max_bytes });
70 }
71 let mut output = vec![0; text.len().min(max_bytes)];
73 let len =
74 base58::decode_into(text, &mut output).map_err(|error| decode_error(error, max_bytes))?;
75 output.truncate(len);
76 Ok(output)
77}
78
79pub fn decode_fixed<const N: usize>(text: &str) -> Result<[u8; N], Error> {
81 base58::decode_fixed(text).map_err(|error| decode_error(error, N))
82}
83
84fn decode_fixed_into(text: &str, output: &mut [u8]) -> Result<(), Error> {
85 let actual =
86 base58::decode_into(text, output).map_err(|error| decode_error(error, output.len()))?;
87 if actual != output.len() {
88 return Err(Error::InvalidLength {
89 expected: output.len(),
90 actual,
91 });
92 }
93 Ok(())
94}
95
96pub fn parse_address(text: &str) -> Result<[u8; 32], Error> {
100 if text.len() == 56
101 && let Ok((version, payload)) = stellar_strkey::convert::decode::<32, 35>(text.as_bytes())
102 && version == ADDRESS_STRKEY_VERSION
103 && let Ok(bytes) = <[u8; 32]>::try_from(payload.as_slice())
104 && address_strkey(&bytes) == text
105 {
106 return Ok(bytes);
107 }
108 decode_fixed(text).map_err(|_| Error::InvalidTypedValue { prefix: 'Z' })
109}
110
111pub fn parse_public_key(text: &str) -> Result<[u8; 32], Error> {
113 if let Ok(key) = text.parse::<stellar_strkey::ed25519::PublicKey>() {
114 return Ok(key.0);
115 }
116 decode_fixed(text).map_err(|_| Error::InvalidTypedValue { prefix: 'G' })
117}
118
119pub fn parse_secret_seed(text: &str) -> Result<Zeroizing<[u8; 32]>, Error> {
123 if let Ok(key) = text.parse::<stellar_strkey::ed25519::PrivateKey>() {
124 return Ok(Zeroizing::new(key.0));
125 }
126 let mut output = Zeroizing::new([0; 32]);
127 decode_fixed_into(text, output.as_mut_slice())
128 .map_err(|_| Error::InvalidTypedValue { prefix: 'S' })?;
129 Ok(output)
130}
131
132pub fn address_strkey(bytes: &[u8; 32]) -> String {
136 stellar_strkey::convert::encode::<32, 35, 56>(ADDRESS_STRKEY_VERSION, bytes).to_string()
137}
138
139pub fn public_key_strkey(bytes: &[u8; 32]) -> String {
141 stellar_strkey::ed25519::PublicKey(*bytes)
142 .to_string()
143 .as_str()
144 .to_owned()
145}
146
147pub fn secret_seed_strkey(bytes: &[u8; 32]) -> Zeroizing<String> {
149 let key = stellar_strkey::ed25519::PrivateKey(*bytes);
150 let mut output = Zeroizing::default();
151 key.as_unredacted().write_string(&mut output);
152 Zeroizing::new(output.as_str().to_owned())
153}
154
155#[cfg(test)]
156mod tests;