use super::{Error, GenericDiagram, Result};
use crate::macros::ImpDeref;
use bitcoin::hashes::{Hash, sha256};
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ComplexDiagram(pub [[Option<String>; 7]; 7]);
ImpDeref!(ComplexDiagram, [[Option<String>; 7]; 7]);
impl GenericDiagram for ComplexDiagram {
type Item = String;
fn to_bytes(&self) -> Result<Vec<u8>> {
let mut str_list: Vec<&str> = vec![];
let mut str_lens: Vec<u8> = vec![];
let mut indices: [u8; 7] = [0; 7];
(0..7).rev().try_for_each(|col| {
(0..7).rev().try_for_each(|row| {
if let Some(s) = &self[row][col]
&& !s.is_empty()
{
if s.len() > u8::MAX as usize {
return Err(Error::StringTooLong(s.to_string()));
}
str_list.push(s);
str_lens.push(s.len() as u8);
indices[row] |= 1 << (6 - col);
}
Ok(())
})
})?;
indices[0] |= 1 << 7; let mut secret = [str_list.join("").as_bytes(), &str_lens[..], &indices[..]].concat();
let check = sha256::Hash::hash(&secret).as_byte_array()[0];
secret.push(check);
Ok(secret)
}
}
impl ComplexDiagram {
pub const CELL_CHARS_LIMIT: usize = 50;
#[deprecated(since = "1.7.2", note = "Use `Diagram` instead")]
pub fn new() -> Self {
Self(core::array::from_fn(|_| core::array::from_fn(|_| None)))
}
#[deprecated(since = "1.7.2", note = "Use `Diagram` instead")]
pub fn from_values(items: &[&str], indices: &[(usize, usize)]) -> Self {
let mut diagram = Self(core::array::from_fn(|_| core::array::from_fn(|_| None)));
indices.iter().zip(items).for_each(|(&(r, c), &s)| {
diagram[r][c] = match s.is_empty() {
false => Some(s.to_owned()),
true => None,
}
});
diagram
}
}
#[cfg(test)]
#[allow(deprecated)]
mod complex_diagram_test {
use super::*;
use bitcoin::hex::DisplayHex;
#[test]
fn test_complex_diagram() -> Result<()> {
const STR_LIST: &[&str] = &["ABC", "123", "测试", "混A1", "A&*王😊"];
const INDICES: &[(usize, usize)] = &[(0, 6), (1, 1), (1, 3), (4, 2), (6, 6)];
const SECRET_HEX: &str =
"41262ae78e8bf09f988a414243e6b58be8af95e6b7b741313132330a0306050381280000100001c8";
let cdm = ComplexDiagram::from_values(STR_LIST, INDICES);
assert_eq!(cdm.to_bytes()?.to_lower_hex_string(), SECRET_HEX);
assert_eq!(cdm[6][6], Some(STR_LIST[4].to_owned()));
#[cfg(not(feature = "testnet"))]
{
let master = "xprv9s21ZrQH143K2CKVamMeHC5bmLWCDx4dLohvk3cVABv8NCFpyBpzeRi3uiZT5gmGz9rgpHZTBWCDXqCD5PaG93yE69qoRotCNqX6F4qwU7P";
assert_eq!(cdm.to_master(&[])?.to_string(), master);
let master_v1 = "xprv9s21ZrQH143K3DUYRs4DRSY7JGsaL6FrXCAwuv1ZseQdHs6NJTX7wg99bUp7z3zgRUFwJTZfZp3Zhs9nrsfK6rFdkY78tbkAr1ovxALxUJu";
assert_eq!(cdm.to_master_v1(&[])?.to_string(), master_v1);
}
Ok(())
}
#[test]
fn test_complex_entropy() -> Result<()> {
const STR_LIST: &[&str] = &["ABC", "混A1", "123", "测试", "A&*王😊"];
const INDICES: &[(usize, usize)] = &[(0, 6), (1, 1), (1, 3), (4, 2), (6, 0)];
const SECRET_HEX: &str =
"414243313233e6b58be8af95e6b7b7413141262ae78e8bf09f988a030306050a8128000010004052";
const RAW_ENTROPY: &str =
"58affc63a2c628806721390ece8f446b0e7f133962e018a3a32e3d888b391791";
const SALT_STR: &str = "123abc";
const SALT_ENTROPY: &str =
"4f76acb612ba64c5f7612778666ce4055a5c465544653affad99f206daa836a5";
let cdm = ComplexDiagram::from_values(STR_LIST, INDICES);
assert_eq!(cdm.to_bytes()?.to_lower_hex_string(), SECRET_HEX);
let entropy = cdm.to_entropy(Default::default())?;
assert_eq!(entropy.to_lower_hex_string(), RAW_ENTROPY);
let salt_entropy = cdm.to_entropy(SALT_STR.as_bytes())?;
assert_eq!(salt_entropy.to_lower_hex_string(), SALT_ENTROPY);
#[cfg(not(feature = "testnet"))]
{
let master = "xprv9s21ZrQH143K2xfe53kNiHqjGC6Jv8zMfjAVhPQZw1nur4CnM79LqpSKZpURNEvyC4xjpPGj37efmviNjkpK6mv9LgRyJBSB8rSK18w6yaY";
assert_eq!(cdm.to_master(SALT_STR.as_bytes())?.to_string(), master);
let master_v1 = "xprv9s21ZrQH143K32BBNz2hduzSS7p8q18MtvDzyGvHFKvMfLRKaS7Bk27BhbMb47X5qeBpEmSiFtsbRv9Zw6QoMDbTEyNo1BU5Qka1PQvAZ4u";
assert_eq!(
cdm.to_master_v1(SALT_STR.as_bytes())?.to_string(),
master_v1
);
}
Ok(())
}
}