radix_common/address/
encoder.rs

1use super::hrpset::HrpSet;
2use crate::address::errors::AddressBech32EncodeError;
3use crate::network::NetworkDefinition;
4use crate::types::EntityType;
5use bech32::{self, ToBase32, Variant, WriteBase32};
6use sbor::rust::prelude::*;
7
8/// Represents an encoder which understands how to encode Scrypto addresses in Bech32.
9#[derive(Debug)]
10pub struct AddressBech32Encoder {
11    pub hrp_set: HrpSet,
12}
13
14impl AddressBech32Encoder {
15    pub fn for_simulator() -> Self {
16        Self::new(&NetworkDefinition::simulator())
17    }
18
19    /// Instantiates a new AddressBech32Encoder with the HRP corresponding to the passed network.
20    pub fn new(network: &NetworkDefinition) -> Self {
21        Self {
22            hrp_set: network.into(),
23        }
24    }
25
26    pub fn encode(&self, full_data: &[u8]) -> Result<String, AddressBech32EncodeError> {
27        let mut buf = String::new();
28        self.encode_to_fmt(&mut buf, full_data)?;
29        Ok(buf)
30    }
31
32    /// Low level method which performs the Bech32 encoding of the data.
33    pub fn encode_to_fmt<F: fmt::Write>(
34        &self,
35        fmt: &mut F,
36        full_data: &[u8],
37    ) -> Result<(), AddressBech32EncodeError> {
38        // Decode the entity type
39        let entity_type = EntityType::from_repr(
40            *full_data
41                .get(0)
42                .ok_or(AddressBech32EncodeError::MissingEntityTypeByte)?,
43        )
44        .ok_or_else(|| AddressBech32EncodeError::InvalidEntityTypeId(full_data[0]))?;
45
46        // Obtain the HRP corresponding to this entity type
47        let hrp = self.hrp_set.get_entity_hrp(&entity_type);
48
49        match bech32_encode_to_fmt(fmt, hrp, full_data.to_base32(), Variant::Bech32m) {
50            Ok(Ok(())) => Ok(()),
51            Ok(Err(format_error)) => Err(AddressBech32EncodeError::FormatError(format_error)),
52            Err(encoding_error) => Err(AddressBech32EncodeError::Bech32mEncodingError(
53                encoding_error,
54            )),
55        }
56    }
57}
58
59/**
60 * NOTE:
61 * The below code is copied with minor alterations from the bech32 crate.
62 * These alterations are to avoid using std for allocations, and fit with the sbor no-alloc options.
63 *
64 * The original source for the bech32 crate is under MIT license: https://crates.io/crates/bech32
65 * This license permits modification without restriction, but requires the license copying below.
66 *
67 * Important additional note - the use of this modified code is also covered under the Radix license,
68 * as per all code in this repository.
69 *
70 * -----------------
71 *
72 * MIT License
73 *
74 * Copyright (c) [year] [fullname]
75 *
76 * Permission is hereby granted, free of charge, to any person obtaining a copy
77 * of this software and associated documentation files (the "Software"), to deal
78 * in the Software without restriction, including without limitation the rights
79 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80 * copies of the Software, and to permit persons to whom the Software is
81 * furnished to do so, subject to the following conditions:
82 *
83 * The above copyright notice and this permission notice shall be included in all
84 * copies or substantial portions of the Software.
85 *
86 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
92 * SOFTWARE.
93 */
94
95/// Encode a bech32 payload to an [fmt::Write].
96/// This method is intended for implementing traits from [std::fmt].
97/// This method uses the std::fmt traits
98///
99/// # Errors
100/// * If [check_hrp] returns an error for the given HRP.
101/// # Deviations from standard
102/// * No length limits are enforced for the data part
103pub fn bech32_encode_to_fmt<F: fmt::Write, T: AsRef<[bech32::u5]>>(
104    fmt: &mut F,
105    hrp: &str,
106    data: T,
107    variant: Variant,
108) -> Result<fmt::Result, bech32::Error> {
109    let hrp_lower = match bech32_check_hrp(hrp)? {
110        Bech32Case::Upper => Cow::Owned(hrp.to_lowercase()),
111        Bech32Case::Lower | Bech32Case::None => Cow::Borrowed(hrp),
112    };
113
114    match bech32::Bech32Writer::new(&hrp_lower, variant, fmt) {
115        Ok(mut writer) => {
116            Ok(writer.write(data.as_ref()).and_then(|_| {
117                // Finalize manually to avoid panic on drop if write fails
118                writer.finalize()
119            }))
120        }
121        Err(e) => Ok(Err(e)),
122    }
123}
124
125/// Check if the HRP is valid. Returns the case of the HRP, if any.
126///
127/// # Errors
128/// * **MixedCase**: If the HRP contains both uppercase and lowercase characters.
129/// * **InvalidChar**: If the HRP contains any non-ASCII characters (outside 33..=126).
130/// * **InvalidLength**: If the HRP is outside 1..83 characters long.
131fn bech32_check_hrp(hrp: &str) -> Result<Bech32Case, bech32::Error> {
132    if hrp.is_empty() || hrp.len() > 83 {
133        return Err(bech32::Error::InvalidLength);
134    }
135
136    let mut has_lower: bool = false;
137    let mut has_upper: bool = false;
138    for b in hrp.bytes() {
139        // Valid subset of ASCII
140        if !(33..=126).contains(&b) {
141            return Err(bech32::Error::InvalidChar(b as char));
142        }
143
144        if (b'a'..=b'z').contains(&b) {
145            has_lower = true;
146        } else if (b'A'..=b'Z').contains(&b) {
147            has_upper = true;
148        };
149
150        if has_lower && has_upper {
151            return Err(bech32::Error::MixedCase);
152        }
153    }
154
155    Ok(match (has_upper, has_lower) {
156        (true, false) => Bech32Case::Upper,
157        (false, true) => Bech32Case::Lower,
158        (false, false) => Bech32Case::None,
159        (true, true) => unreachable!(),
160    })
161}
162
163#[derive(Clone, Copy, PartialEq, Eq)]
164enum Bech32Case {
165    Upper,
166    Lower,
167    None,
168}