use crate::util::luhn::{checksum, is_valid};
use std::{
collections::HashMap,
fmt::{Display, Error, Formatter},
};
use tari_core::transactions::types::PublicKey;
use tari_crypto::tari_utilities::{
hex::{Hex, HexError},
ByteArray,
};
const EMOJI: [char; 256] = [
'🌀', '🌂', '🌈', '🌊', '🌋', '🌍', '🌙', '🌝', '🌞', '🌟', '🌠', '🌰', '🌴', '🌵', '🌷', '🌸', '🌹', '🌻', '🌽',
'🍀', '🍁', '🍄', '🍅', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕',
'🍗', '🍚', '🍞', '🍟', '🍠', '🍣', '🍦', '🍩', '🍪', '🍫', '🍬', '🍭', '🍯', '🍰', '🍳', '🍴', '🍵', '🍶', '🍷',
'🍸', '🍹', '🍺', '🍼', '🎀', '🎁', '🎂', '🎃', '🎄', '🎈', '🎉', '🎒', '🎓', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥',
'🎧', '🎨', '🎩', '🎪', '🎬', '🎭', '🎮', '🎰', '🎱', '🎲', '🎳', '🎵', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽',
'🎾', '🎿', '🏀', '🏁', '🏆', '🏈', '🏉', '🏠', '🏥', '🏦', '🏭', '🏰', '🐀', '🐉', '🐊', '🐌', '🐍', '🐎', '🐐',
'🐑', '🐓', '🐖', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🐢', '🐣', '🐨', '🐩', '🐪', '🐬', '🐭', '🐮',
'🐯', '🐰', '🐲', '🐳', '🐴', '🐵', '🐶', '🐷', '🐸', '🐺', '🐻', '🐼', '🐽', '🐾', '👀', '👅', '👑', '👒', '👓',
'👔', '👕', '👖', '👗', '👘', '👙', '👚', '👛', '👞', '👟', '👠', '👡', '👢', '👣', '👹', '👻', '👽', '👾', '👿',
'💀', '💄', '💈', '💉', '💊', '💋', '💌', '💍', '💎', '💐', '💔', '💕', '💘', '💡', '💣', '💤', '💦', '💨', '💩',
'💭', '💯', '💰', '💳', '💸', '💺', '💻', '💼', '📈', '📉', '📌', '📎', '📚', '📝', '📡', '📣', '📱', '📷', '🔋',
'🔌', '🔎', '🔑', '🔔', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔱', '🗽', '😂', '😇', '😈',
'😉', '😍', '😎', '😱', '😷', '😹', '😻', '😿', '🚀', '🚁', '🚂', '🚌', '🚑', '🚒', '🚓', '🚕', '🚗', '🚜', '🚢',
'🚦', '🚧', '🚨', '🚪', '🚫', '🚲', '🚽', '🚿', '🛁',
];
lazy_static! {
static ref REVERSE_EMOJI: HashMap<char, usize> = {
let mut m = HashMap::with_capacity(256);
EMOJI.iter().enumerate().for_each(|(i, c)| {
m.insert(*c, i);
});
m
};
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct EmojiId(String);
pub const fn emoji_set() -> [char; 256] {
EMOJI
}
impl EmojiId {
pub fn from_pubkey(key: &PublicKey) -> Self {
EmojiId::from_bytes(key.as_bytes())
}
pub fn from_hex(hex_key: &str) -> Result<Self, HexError> {
let key = PublicKey::from_hex(hex_key)?;
Ok(EmojiId::from_pubkey(&key))
}
pub fn to_pubkey(&self) -> PublicKey {
let bytes = self.to_bytes();
PublicKey::from_bytes(&bytes).unwrap()
}
pub fn is_valid(s: &str) -> bool {
EmojiId::str_to_pubkey(s).is_ok()
}
pub fn str_to_pubkey(s: &str) -> Result<PublicKey, ()> {
let mut indices = Vec::with_capacity(33);
for c in s.chars() {
if let Some(i) = REVERSE_EMOJI.get(&c) {
indices.push(*i);
} else {
return Err(());
}
}
if !is_valid(&indices, 256) {
return Err(());
}
let bytes = EmojiId::byte_vec(s)?;
PublicKey::from_bytes(&bytes).map_err(|_| ())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_bytes(&self) -> Vec<u8> {
EmojiId::byte_vec(&self.0).unwrap()
}
fn from_bytes(bytes: &[u8]) -> Self {
let mut vec = Vec::<usize>::with_capacity(33);
bytes.iter().for_each(|b| vec.push((*b) as usize));
let checksum = checksum(&vec, 256);
assert!(checksum < 256);
vec.push(checksum);
let id = vec.iter().map(|b| EMOJI[*b]).collect();
Self(id)
}
fn byte_vec(s: &str) -> Result<Vec<u8>, ()> {
let mut v = Vec::with_capacity(32);
for c in s.chars().take(32) {
if let Some(index) = REVERSE_EMOJI.get(&c) {
v.push(*index as u8);
} else {
return Err(());
}
}
Ok(v)
}
}
impl Display for EmojiId {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
fmt.write_str(self.as_str())
}
}
#[cfg(test)]
mod test {
use crate::util::emoji::EmojiId;
use tari_core::transactions::types::PublicKey;
use tari_crypto::tari_utilities::hex::Hex;
#[test]
fn convert_key() {
let pubkey = PublicKey::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap();
let eid = EmojiId::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap();
assert_eq!(
eid.as_str(),
"🐎🍴🌷🌟💻🐖🐩🐾🌟🐬🎧🐌🏦🐳🐎🐝🐢🔋👕🎸👿🍒🐓🎉💔🌹🏆🐬💡🎳🚦🍹🎒"
);
assert_eq!(EmojiId::from_pubkey(&pubkey), eid);
assert_eq!(
&eid.to_bytes().to_hex(),
"70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a"
);
assert_eq!(
EmojiId::str_to_pubkey("🐎🍴🌷🌟💻🐖🐩🐾🌟🐬🎧🐌🏦🐳🐎🐝🐢🔋👕🎸👿🍒🐓🎉💔🌹🏆🐬💡🎳🚦🍹🎒").unwrap(),
pubkey
);
}
#[test]
fn is_valid() {
let eid = EmojiId::from_hex("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a").unwrap();
assert!(EmojiId::is_valid(eid.as_str()));
assert_eq!(EmojiId::is_valid(""), false, "Emoji ID too short");
assert_eq!(EmojiId::is_valid("🌂"), false, "Emoji ID too short");
assert_eq!(
EmojiId::is_valid("🌟💻🐖🐩🐾🌟🐬🎧🐌🏦🐳🐎🐝🐢🔋👕🎸👿🍒🐓🎉💔🌹🏆🐬💡🎳🚦🍹🎒"),
false,
"Emoji ID too short"
);
assert_eq!(
EmojiId::is_valid("70350e09c474809209824c6e6888707b7dd09959aa227343b5106382b856f73a"),
false,
"Not emoji string"
);
assert_eq!(
EmojiId::is_valid("🐎🍴🌷🌟💻🐖🐩🐾🌟🐬🎧🐌🏦🐳🐎🐝🐢🔋👕🎸👿🍒🐓🎉💔🌹🏆🐬💡🎳🚦🍹"),
false,
"No checksum"
);
assert_eq!(
EmojiId::is_valid("🐎🍴🌷🌟💻🐖🐩🐾🌟🐬🎧🐌🏦🐳🐎🐝🐢🔋👕🎸👿🍒🐓🎉💔🌹🏆🐬💡🎳🚦🍹📝"),
false,
"Wrong checksum"
);
}
}