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}