use crate::Error;
use grin_core::global::max_tx_weight;
use grin_wallet_util::byte_ser;
use regex::Regex;
use sha2::{Digest, Sha256};
use std::str;
use super::types::{Slatepack, SlatepackBin};
pub static HEADER: &str = "BEGINSLATEPACK.";
static FOOTER: &str = ". ENDSLATEPACK.";
const WORD_LENGTH: usize = 15;
const WORDS_PER_LINE: usize = 200;
const WEIGHT_RATIO: u64 = 32;
pub fn max_size() -> u64 {
max_tx_weight()
.saturating_mul(WEIGHT_RATIO)
.saturating_add(HEADER.len() as u64)
.saturating_add(FOOTER.len() as u64)
}
pub fn min_size() -> u64 {
HEADER.len() as u64
}
lazy_static! {
static ref HEADER_REGEX: Regex =
Regex::new(concat!(r"^[>\n\r\t ]*BEGINSLATEPACK[>\n\r\t ]*$")).unwrap();
static ref FOOTER_REGEX: Regex =
Regex::new(concat!(r"^[>\n\r\t ]*ENDSLATEPACK[>\n\r\t ]*$")).unwrap();
static ref WHITESPACE_LIST: [u8; 5] = [b'>', b'\n', b'\r', b'\t', b' '];
}
pub struct SlatepackArmor;
impl SlatepackArmor {
pub fn decode(armor_bytes: &[u8]) -> Result<Vec<u8>, Error> {
let header_bytes = armor_bytes
.iter()
.take_while(|byte| **byte != b'.')
.cloned()
.collect::<Vec<u8>>();
check_header(&header_bytes)?;
let header_len = header_bytes.len() + 1;
let payload_source = armor_bytes
.get(header_len..)
.ok_or_else(|| Error::InvalidSlatepackData("Bad armor header".to_string()))?;
let payload_bytes = payload_source
.iter()
.take_while(|byte| **byte != b'.')
.cloned()
.collect::<Vec<u8>>();
let payload_len = payload_bytes.len();
let consumed_bytes = header_len
.checked_add(payload_len)
.and_then(|v| v.checked_add(1))
.ok_or_else(|| Error::InvalidSlatepackData("Bad armor footer".to_string()))?;
let footer_source = armor_bytes
.get(consumed_bytes..)
.ok_or_else(|| Error::InvalidSlatepackData("Bad armor footer".to_string()))?;
let footer_bytes = footer_source
.iter()
.take_while(|byte| **byte != b'.')
.cloned()
.collect::<Vec<u8>>();
check_footer(&footer_bytes)?;
let clean_payload = payload_bytes
.iter()
.filter(|byte| !WHITESPACE_LIST.contains(byte))
.cloned()
.collect::<Vec<u8>>();
let base_decode = bs58::decode(&clean_payload)
.into_vec()
.map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
if base_decode.len() < 4 {
return Err(Error::SlatepackDeser("Payload too short".into()));
}
let (error_code, slatepack_bytes) = base_decode.split_at(4);
error_check(error_code, slatepack_bytes)?;
Ok(slatepack_bytes.to_vec())
}
pub fn encode(slatepack: &Slatepack) -> Result<String, Error> {
let slatepack_bytes = byte_ser::to_bytes(&SlatepackBin(slatepack.clone()))
.map_err(|_| Error::SlatepackSer)?;
let encoded_slatepack = base58check(&slatepack_bytes)?;
let formatted_slatepack = format_slatepack(&format!("{}{}", HEADER, encoded_slatepack))?;
Ok(format!("{}{}\n", formatted_slatepack, FOOTER))
}
}
fn error_check(error_code: &[u8], slate_bytes: &[u8]) -> Result<(), Error> {
let new_check = generate_check(slate_bytes)?;
if error_code.iter().eq(new_check.iter()) {
Ok(())
} else {
Err(Error::InvalidSlatepackData(
"Bad slate error code- some data was corrupted".to_string(),
))
}
}
fn check_header(header: &[u8]) -> Result<(), Error> {
let framing = str::from_utf8(header).map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
if HEADER_REGEX.is_match(framing) {
Ok(())
} else {
Err(Error::InvalidSlatepackData("Bad armor header".to_string()))
}
}
fn check_footer(footer: &[u8]) -> Result<(), Error> {
let framing = str::from_utf8(footer).map_err(|_| Error::SlatepackDeser("Bad bytes".into()))?;
if FOOTER_REGEX.is_match(framing) {
Ok(())
} else {
Err(Error::InvalidSlatepackData("Bad armor footer".to_string()))
}
}
fn base58check(slate: &[u8]) -> Result<String, Error> {
let mut slate_bytes: Vec<u8> = slate.to_vec();
let mut check_bytes: Vec<u8> = generate_check(&slate_bytes)?;
let mut slate_buf = Vec::new();
slate_buf.append(&mut check_bytes);
slate_buf.append(&mut slate_bytes);
let b58_slate = bs58::encode(slate_buf).into_string();
Ok(b58_slate)
}
fn format_slatepack(slatepack: &str) -> Result<String, Error> {
let formatter = slatepack
.chars()
.enumerate()
.flat_map(|(i, c)| {
if i != 0 && i % WORD_LENGTH == 0 {
if WORDS_PER_LINE != 0 && i % (WORD_LENGTH * WORDS_PER_LINE) == 0 {
Some('\n')
} else {
Some(' ')
}
} else {
None
}
.into_iter()
.chain(std::iter::once(c))
})
.collect::<String>();
Ok(formatter)
}
fn generate_check(payload: &[u8]) -> Result<Vec<u8>, Error> {
let mut first_hasher = Sha256::new();
first_hasher.update(payload);
let mut second_hasher = Sha256::new();
second_hasher.update(first_hasher.finalize());
let checksum = second_hasher.finalize();
let check_bytes: Vec<u8> = checksum[0..4].to_vec();
Ok(check_bytes)
}