use std::{
collections::HashMap,
convert::TryFrom,
fmt::{Display, Error, Formatter},
iter,
str::FromStr,
};
use once_cell::sync::Lazy;
use tari_crypto::tari_utilities::ByteArray;
use thiserror::Error;
use crate::{
dammsum::{CHECKSUM_BYTES, compute_checksum, validate_checksum},
types::{CompressedPublicKey, UncompressedPublicKey},
};
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct EmojiId(CompressedPublicKey);
const DICT_SIZE: usize = 256; const DATA_BYTES: usize = 32;
pub const EMOJI: [char; DICT_SIZE] = [
'🐢', '📟', '🌈', '🌊', '🎯', '🐋', '🌙', '🤔', '🌕', '⭐', '🎋', '🌰', '🌴', '🌵', '🌲', '🌸', '🌹', '🌻', '🌽',
'🍀', '🍁', '🍄', '🥑', '🍆', '🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🍎', '🍐', '🍑', '🍒', '🍓', '🍔', '🍕',
'🍗', '🍚', '🍞', '🍟', '🥝', '🍣', '🍦', '🍩', '🍪', '🍫', '🍬', '🍭', '🍯', '🥐', '🍳', '🥄', '🍵', '🍶', '🍷',
'🍸', '🍾', '🍺', '🍼', '🎀', '🎁', '🎂', '🎃', '🤖', '🎈', '🎉', '🎒', '🎓', '🎠', '🎡', '🎢', '🎣', '🎤', '🎥',
'🎧', '🎨', '🎩', '🎪', '🎬', '🎭', '🎮', '🎰', '🎱', '🎲', '🎳', '🎵', '🎷', '🎸', '🎹', '🎺', '🎻', '🎼', '🎽',
'🎾', '🎿', '🏀', '🏁', '🏆', '🏈', '⚽', '🏠', '🏥', '🏦', '🏭', '🏰', '🐀', '🐉', '🐊', '🐌', '🐍', '🦁', '🐐',
'🐑', '🐔', '🙈', '🐗', '🐘', '🐙', '🐚', '🐛', '🐜', '🐝', '🐞', '🦋', '🐣', '🐨', '🦀', '🐪', '🐬', '🐭', '🐮',
'🐯', '🐰', '🦆', '🦂', '🐴', '🐵', '🐶', '🐷', '🐸', '🐺', '🐻', '🐼', '🐽', '🐾', '👀', '👅', '👑', '👒', '🧢',
'💅', '👕', '👖', '👗', '👘', '👙', '💃', '👛', '👞', '👟', '👠', '🥊', '👢', '👣', '🤡', '👻', '👽', '👾', '🤠',
'👃', '💄', '💈', '💉', '💊', '💋', '👂', '💍', '💎', '💐', '💔', '🔒', '🧩', '💡', '💣', '💤', '💦', '💨', '💩',
'➕', '💯', '💰', '💳', '💵', '💺', '💻', '💼', '📈', '📜', '📌', '📎', '📖', '📿', '📡', '⏰', '📱', '📷', '🔋',
'🔌', '🚰', '🔑', '🔔', '🔥', '🔦', '🔧', '🔨', '🔩', '🔪', '🔫', '🔬', '🔭', '🔮', '🔱', '🗽', '😂', '😇', '😈',
'🤑', '😍', '😎', '😱', '😷', '🤢', '👍', '👶', '🚀', '🚁', '🚂', '🚚', '🚑', '🚒', '🚓', '🛵', '🚗', '🚜', '🚢',
'🚦', '🚧', '🚨', '🚪', '🚫', '🚲', '🚽', '🚿', '🧲',
];
pub static REVERSE_EMOJI: Lazy<HashMap<char, u8>> = Lazy::new(|| {
let mut m = HashMap::with_capacity(DICT_SIZE);
EMOJI.iter().enumerate().for_each(|(i, c)| {
m.insert(*c, u8::try_from(i).expect("Invalid emoji"));
});
m
});
pub const fn emoji_set() -> [char; DICT_SIZE] {
EMOJI
}
#[derive(Debug, Error, PartialEq)]
pub enum EmojiIdError {
#[error("Invalid size")]
InvalidSize,
#[error("Invalid emoji character")]
InvalidEmoji,
#[error("Invalid checksum")]
InvalidChecksum,
#[error("Cannot recover public key")]
CannotRecoverPublicKey,
}
impl EmojiId {
pub fn as_public_key(&self) -> &CompressedPublicKey {
&self.0
}
}
impl FromStr for EmojiId {
type Err = EmojiIdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.chars().count() != DATA_BYTES + CHECKSUM_BYTES {
return Err(EmojiIdError::InvalidSize);
}
let mut bytes = Vec::<u8>::with_capacity(DATA_BYTES + CHECKSUM_BYTES);
for c in s.chars() {
if let Some(i) = REVERSE_EMOJI.get(&c) {
bytes.push(*i);
} else {
return Err(EmojiIdError::InvalidEmoji);
}
}
let data = validate_checksum(&bytes).map_err(|_| EmojiIdError::InvalidChecksum)?;
match UncompressedPublicKey::from_canonical_bytes(data) {
Ok(public_key) => Ok(Self(CompressedPublicKey::new_from_pk(public_key))),
Err(_) => Err(EmojiIdError::CannotRecoverPublicKey),
}
}
}
impl From<&CompressedPublicKey> for EmojiId {
fn from(value: &CompressedPublicKey) -> Self {
Self::from(value.clone())
}
}
impl From<CompressedPublicKey> for EmojiId {
fn from(value: CompressedPublicKey) -> Self {
Self(value)
}
}
impl From<&EmojiId> for CompressedPublicKey {
fn from(value: &EmojiId) -> Self {
value.as_public_key().clone()
}
}
impl Display for EmojiId {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> {
let bytes = self.as_public_key().as_bytes();
let emoji = bytes
.iter()
.chain(iter::once(&compute_checksum(bytes)))
.map(|b| EMOJI.get(*b as usize).expect("Should exits"))
.collect::<String>();
fmt.write_str(&emoji)
}
}
#[cfg(test)]
mod test {
#![allow(clippy::indexing_slicing)]
use std::{iter, str::FromStr};
use tari_crypto::{keys::SecretKey, tari_utilities::ByteArray};
use crate::{
dammsum::{CHECKSUM_BYTES, compute_checksum},
emoji::{DATA_BYTES, EmojiId, EmojiIdError, emoji_set},
types::{CompressedPublicKey, PrivateKey, UncompressedPublicKey},
};
#[test]
fn valid_emoji_id() {
let mut rng = rand::thread_rng();
let public_key = CompressedPublicKey::from_secret_key(&PrivateKey::random(&mut rng));
let emoji_id_from_public_key = EmojiId::from(&public_key);
assert_eq!(emoji_id_from_public_key.as_public_key(), &public_key);
let emoji_string = emoji_id_from_public_key.to_string();
assert_eq!(emoji_string.chars().count(), DATA_BYTES + CHECKSUM_BYTES);
let emoji_id_from_emoji_string = EmojiId::from_str(&emoji_string).unwrap();
assert_eq!(emoji_id_from_emoji_string.to_string(), emoji_string);
assert_eq!(emoji_id_from_emoji_string.as_public_key(), &public_key);
}
#[test]
fn invalid_size() {
let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒";
assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidSize));
}
#[test]
fn invalid_emoji() {
let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎅";
assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidEmoji));
}
#[test]
fn invalid_checksum() {
let emoji_string = "🌴🦀🔌📌🚑🌰🎓🌴🐊🐌🔒💡🐜📜👛🍵👛🐽🎂🐻🐢🍓👶🐭🐼🏀🎪💔💵🥑🔋🎒🎒";
assert_eq!(EmojiId::from_str(emoji_string), Err(EmojiIdError::InvalidChecksum));
}
#[test]
fn invalid_public_key() {
let mut bytes = vec![0u8; DATA_BYTES];
bytes[0] = 1;
assert!(UncompressedPublicKey::from_canonical_bytes(&bytes).is_err());
let emoji_set = emoji_set();
let emoji_string = bytes
.iter()
.chain(iter::once(&compute_checksum(&bytes)))
.map(|b| emoji_set[*b as usize])
.collect::<String>();
assert_eq!(
EmojiId::from_str(&emoji_string),
Err(EmojiIdError::CannotRecoverPublicKey)
);
}
#[test]
fn data_size() {
assert_eq!(CompressedPublicKey::default().as_bytes().len(), DATA_BYTES);
}
}