Copy /// Codes are referenced from https://github.com/bitcoinjs/bech32/blob/master/src/index.ts
use std::collections::HashMap;
use std::sync::LazyLock;
const MANTRA_ADDRESS_PREFIX: &str = "mantra";
/// These characters are carefully designed as part of the BIP173 specification to avoid common transcription errors.
/// Reference: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
const ALPHABET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
static ALPHABET_MAP: LazyLock<HashMap<char, u8>> = LazyLock::new(|| {
let mut map: HashMap<char, u8> = HashMap::new();
for (z, x) in ALPHABET.chars().enumerate() {
map.insert(x, z as u8);
}
map
});
/// Convert EVM address to MANTRA address.
/// References:
/// - https://docs.cronos.org/for-dapp-developers/chain-integration/adress-conversion
/// - https://en.bitcoin.it/wiki/BIP_0173
///
/// # Arguments
/// * `evm_address` - The EVM address as a hex string (with or without 0x prefix)
///
/// # Returns
/// * `Result<String, Box<dyn std::error::Error>>` - The MANTRA address or an error
pub fn convert_evm_address_to_mantra_address(
evm_address: &str,
) -> Result<String, Box<dyn std::error::Error>> {
// Remove "0x" prefix if present
let hex_str = evm_address.trim_start_matches("0x").to_ascii_lowercase();
// Convert hex string to bytes
let evm_address_bytes = hex::decode(hex_str)?;
// Convert bits from 8 to 5
let converted_bits = convert_bits(&evm_address_bytes, 8, 5, true)?;
// Encode using bech32
let mantra_address = bech32_encoder(MANTRA_ADDRESS_PREFIX, &converted_bits, None)?;
Ok(mantra_address)
}
#[allow(dead_code)]
pub fn convert_mantra_address_to_eth_address(
mantra_address: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let decoded: Decoded = bech32_decoder(mantra_address, None)?;
let hex_bytes = convert_bits(&decoded.words, 5, 8, false)?;
Ok(format!("0x{}", hex::encode(hex_bytes)))
}
/// General power-of-2 base conversion.
/// References:
/// - https://en.bitcoin.it/wiki/Bech32
/// - https://github.com/fiatjaf/bech32/blob/master/bech32/__init__.py
///
/// # Arguments
/// * `data` - Input data as a slice of bytes
/// * `from_bits` - Number of bits in source representation
/// * `to_bits` - Number of bits in target representation
/// * `pad` - Whether to pad a result if needed
///
/// # Returns
/// * `Result<Vec<u8>, Box<dyn std::error::Error>>` - Converted data or error if conversion fails
pub fn convert_bits(
data: &[u8],
from_bits: u32,
to_bits: u32,
pad: bool,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut acc = 0u32;
let mut bits = 0u32;
let mut ret = Vec::new();
let maxv = (1 << to_bits) - 1;
let max_acc = (1 << (from_bits + to_bits - 1)) - 1;
for &value in data {
let value = value as u32;
if value >= (1 << from_bits) {
return Err("Invalid data for base conversion: value out of range".into());
}
acc = (acc << from_bits | value) & max_acc;
bits += from_bits;
while bits >= to_bits {
bits -= to_bits;
ret.push(((acc >> bits) & maxv) as u8);
}
}
if pad && bits > 0 {
ret.push(((acc << (to_bits - bits)) & maxv) as u8);
} else if !pad && (bits >= from_bits || (acc << (to_bits - bits)) & maxv != 0) {
return Err("Invalid padding in base conversion".into());
}
Ok(ret)
}
fn bech32_encoder(prefix: &str, words: &[u8], limit: Option<usize>) -> Result<String, String> {
let limit = limit.unwrap_or(90);
if prefix.len() + 7 + words.len() > limit {
return Err("Exceeds length limit".to_string());
}
let prefix = prefix.to_lowercase();
// determine chk mod
let mut chk = prefix_chk(&prefix).map_err(|e| e)?;
let mut result = format!("{}1", prefix);
for &x in words {
if x >> 5 != 0 {
return Err("Non-5-bit word".to_string());
}
chk = polymod_step(chk) ^ (x as u32);
result.push(ALPHABET.chars().nth(x as usize).unwrap());
}
chk = (0..6).fold(chk, |chk, _| polymod_step(chk)) ^ 1;
for i in 0..6 {
let v = (chk >> ((5 - i) * 5)) & 0x1f;
result.push(ALPHABET.chars().nth(v as usize).unwrap());
}
Ok(result)
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct Decoded {
pub prefix: String,
pub words: Vec<u8>,
}
#[allow(dead_code)]
pub fn bech32_decoder(str_input: &str, limit: Option<usize>) -> Result<Decoded, String> {
let limit = limit.unwrap_or(90);
if str_input.len() < 8 {
return Err(format!("{} too short", str_input));
}
if str_input.len() > limit {
return Err("Exceeds length limit".to_string());
}
// Don't allow mixed case
let lowered = str_input.to_lowercase();
let uppered = str_input.to_uppercase();
if str_input != lowered && str_input != uppered {
return Err(format!("Mixed-case string {}", str_input));
}
let str_normalized = &lowered;
let split = match str_normalized.rfind('1') {
Some(pos) => pos,
None => return Err(format!("No separator character for {}", str_normalized)),
};
if split == 0 {
return Err(format!("Missing prefix for {}", str_normalized));
}
let prefix = str_normalized[..split].to_string();
let word_chars = &str_normalized[split + 1..];
if word_chars.len() < 6 {
return Err("Data too short".to_string());
}
let mut chk = match prefix_chk(&prefix) {
Ok(checksum) => checksum,
Err(error) => return Err(error),
};
let mut words = Vec::new();
for (i, c) in word_chars.chars().enumerate() {
let v = match ALPHABET_MAP.get(&c) {
Some(value) => *value,
None => return Err(format!("Unknown character {}", c)),
};
chk = polymod_step(chk) ^ (v as u32);
// Not in the checksum?
if i + 6 >= word_chars.len() {
continue;
}
words.push(v);
}
if chk != 1 {
return Err(format!("Invalid checksum for {}", str_normalized));
}
Ok(Decoded { prefix, words })
}
fn polymod_step(pre: u32) -> u32 {
let b = pre >> 25;
((pre & 0x1ffffff) << 5)
^ (if ((b >> 0) & 1) != 0 { 0x3b6a57b2 } else { 0 })
^ (if ((b >> 1) & 1) != 0 { 0x26508e6d } else { 0 })
^ (if ((b >> 2) & 1) != 0 { 0x1ea119fa } else { 0 })
^ (if ((b >> 3) & 1) != 0 { 0x3d4233dd } else { 0 })
^ (if ((b >> 4) & 1) != 0 { 0x2a1462b3 } else { 0 })
}
fn prefix_chk(prefix: &str) -> Result<u32, String> {
let mut chk: u32 = 1;
for &code in prefix.as_bytes() {
if code < 33 || code > 126 {
return Err(format!("Invalid prefix ({})", prefix));
}
chk = polymod_step(chk) ^ (code as u32 >> 5);
}
chk = polymod_step(chk);
for &code in prefix.as_bytes() {
chk = polymod_step(chk) ^ (code as u32 & 0x1f);
}
Ok(chk)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_evm_address_to_mantra_address() {
let evm_address = "0x1448b2449076672aCD167b91406c09552101C5C9";
let mantra_address = "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv";
let result = convert_evm_address_to_mantra_address(evm_address);
assert!(result.is_ok());
let converted_mantra_address = result.unwrap();
assert!(mantra_address.starts_with("mantra"));
assert!(mantra_address.len() > 6); // More than just the prefix
assert_eq!(converted_mantra_address.as_str(), mantra_address);
}
#[test]
fn test_convert_evm_address_without_0x_prefix() {
let evm_address = "FaAEcfd80e8c8572bA96657c84eCc9003Ed1b27B";
let mantra_address = "mantra1l2hvlkqw3jzh9w5kv47gfmxfqqldrvnmkun5lg";
let result = convert_evm_address_to_mantra_address(evm_address);
assert!(result.is_ok());
let converted_mantra_address = result.unwrap();
assert!(mantra_address.starts_with("mantra"));
assert!(mantra_address.len() > 6); // More than just the prefix
assert_eq!(converted_mantra_address.as_str(), mantra_address);
}
#[test]
fn test_convert_bits() {
let data = vec![0xFF, 0xFF];
let result = convert_bits(&data, 8, 5, true);
assert!(result.is_ok());
}
#[test]
fn test_convert_bits_invalid_data() {
let data = vec![0xFF]; // 8 bits but trying to convert from 4 bits
let result = convert_bits(&data, 4, 8, true);
assert!(result.is_err());
}
#[test]
fn test_convert_mantra_address_to_eth_address() {
let mantra_address = "mantra1z3yty3yswenj4ngk0wg5qmqf25ssr3wfqayuhv";
let expected_eth_address = "0x1448b2449076672acd167b91406c09552101c5c9";
let result = convert_mantra_address_to_eth_address(mantra_address);
assert!(result.is_ok());
let converted_eth_address = result.unwrap();
assert_eq!(converted_eth_address.to_lowercase(), expected_eth_address);
}
}