use crate::cesr::signing::signer::Signer;
use crate::cesr::{mtr_dex, raw_size, BaseMatter, Parsable, Tiers};
use crate::errors::MatterError;
use crate::Matter;
use sodiumoxide::crypto::pwhash::argon2id13 as pwhash;
use sodiumoxide::crypto::pwhash::argon2id13::Salt;
use sodiumoxide::randombytes;
use std::any::Any;
#[derive(Debug, Clone)]
pub struct Salter {
base: BaseMatter,
tier: Tiers,
}
impl Salter {
pub fn new(
raw: Option<&[u8]>,
code: Option<&str>,
tier: Option<Tiers>,
) -> Result<Self, MatterError> {
let code = code.unwrap_or(mtr_dex::SALT_128);
let raw = match raw {
Some(r) => r,
None => {
if code == mtr_dex::SALT_128 {
if sodiumoxide::init().is_err() {
return Err(MatterError::CryptoError(
"Sodium initialization failed".to_string(),
));
}
let salt_bytes = randombytes::randombytes(pwhash::SALTBYTES);
&salt_bytes.clone()[..]
} else {
return Err(MatterError::ValidationError(format!(
"Unsupported salter code = {}",
code
)));
}
}
};
if code != mtr_dex::SALT_128 {
return Err(MatterError::ValidationError(format!(
"Unsupported salter code = {}",
code
)));
}
let tier = tier.unwrap_or(Tiers::LOW);
let base = BaseMatter::new(Some(raw), Some(code), None, None)?;
Ok(Self { base, tier })
}
pub fn stretch(
&self,
size: usize,
path: &str,
tier: Option<&Tiers>,
temp: bool,
) -> Result<Vec<u8>, MatterError> {
let tier = tier.unwrap_or(&self.tier);
let (opslimit, memlimit) = if temp {
(pwhash::OpsLimit(1), pwhash::MemLimit(8192))
} else {
match tier {
Tiers::LOW => (pwhash::OPSLIMIT_INTERACTIVE, pwhash::MEMLIMIT_INTERACTIVE),
Tiers::MED => (pwhash::OPSLIMIT_MODERATE, pwhash::MEMLIMIT_MODERATE),
Tiers::HIGH => (pwhash::OPSLIMIT_SENSITIVE, pwhash::MEMLIMIT_SENSITIVE),
}
};
if sodiumoxide::init().is_err() {
return Err(MatterError::CryptoError(
"Sodium initialization failed".to_string(),
));
}
let path_bytes = path.as_bytes();
let salt = match Salt::from_slice(self.raw()) {
Some(s) => s,
None => {
return Err(MatterError::ValidationError(
"Invalid salt size".to_string(),
))
}
};
let mut kb = vec![0u8; size];
let kb = kb.as_mut_slice();
let seed = pwhash::derive_key(kb, path_bytes, &salt, opslimit, memlimit)
.map_err(|_| MatterError::Conversion("Key derivation failed".to_string()))?;
Ok(seed.to_vec())
}
pub fn signer(
&self,
code: Option<&str>,
transferable: Option<bool>,
path: &str,
tier: Option<&Tiers>,
temp: bool,
) -> Result<Signer, MatterError> {
let code = code.unwrap_or(mtr_dex::ED25519_SEED);
let transferable = transferable.unwrap_or(true);
let size = raw_size(code)?;
let seed = self.stretch(size, path, tier, temp)?;
Signer::new(Some(&seed), Some(code), Some(transferable))
}
pub fn signers(
&self,
count: usize,
start: usize,
path: &str,
code: Option<&str>,
transferable: Option<bool>,
tier: Option<&Tiers>,
temp: bool,
) -> Result<Vec<Signer>, MatterError> {
let mut signers = Vec::with_capacity(count);
for i in 0..count {
let path_with_suffix = format!("{}{:x}", path, i + start);
let signer = self.signer(code, transferable, &path_with_suffix, tier, temp)?;
signers.push(signer);
}
Ok(signers)
}
pub fn tier(&self) -> &Tiers {
&self.tier
}
pub fn from_qb64_and_tier(data: &str, tier: Option<Tiers>) -> Result<Self, MatterError> {
let tier = tier.unwrap_or(Tiers::LOW);
let base = BaseMatter::from_qb64(data)?;
if base.code() != mtr_dex::SALT_128 {
return Err(MatterError::ValidationError(format!(
"Unsupported salter code = {}",
base.code()
)));
}
Ok(Self { base, tier })
}
}
impl Parsable for Salter {
fn from_qb64b(data: &mut Vec<u8>, strip: Option<bool>) -> Result<Self, MatterError> {
let base = BaseMatter::from_qb64b(data, strip)?;
if base.code() != mtr_dex::SALT_128 {
return Err(MatterError::ValidationError(format!(
"Unsupported salter code = {}",
base.code()
)));
}
Ok(Self {
base,
tier: Tiers::LOW,
})
}
fn from_qb2(data: &mut Vec<u8>, strip: Option<bool>) -> Result<Self, MatterError> {
let base = BaseMatter::from_qb2(data, strip)?;
if base.code() != mtr_dex::SALT_128 {
return Err(MatterError::ValidationError(format!(
"Unsupported salter code = {}",
base.code()
)));
}
Ok(Self {
base,
tier: Tiers::LOW,
})
}
}
impl Matter for Salter {
fn code(&self) -> &str {
self.base.code()
}
fn raw(&self) -> &[u8] {
self.base.raw()
}
fn qb64(&self) -> String {
self.base.qb64()
}
fn qb64b(&self) -> Vec<u8> {
self.base.qb64b()
}
fn qb2(&self) -> Vec<u8> {
self.base.qb2()
}
fn soft(&self) -> &str {
self.base.soft()
}
fn full_size(&self) -> usize {
self.base.full_size()
}
fn size(&self) -> usize {
self.base.size()
}
fn is_transferable(&self) -> bool {
self.base.is_transferable()
}
fn is_digestive(&self) -> bool {
self.base.is_digestive()
}
fn is_prefixive(&self) -> bool {
self.base.is_prefixive()
}
fn is_special(&self) -> bool {
self.base.is_special()
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_salter_creation() {
let salter = Salter::new(None, None, None).unwrap();
assert_eq!(salter.code(), mtr_dex::SALT_128);
assert_eq!(salter.raw().len(), 16);
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
assert_eq!(salter.code(), mtr_dex::SALT_128);
assert_eq!(salter.raw(), raw);
}
#[test]
fn test_stretching() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let stretched = salter.stretch(32, "test-path", None, true).unwrap();
assert_eq!(stretched.len(), 32);
let stretched2 = salter.stretch(32, "test-path", None, true).unwrap();
assert_eq!(stretched, stretched2);
let stretched3 = salter.stretch(32, "different-path", None, true).unwrap();
assert_ne!(stretched, stretched3);
}
#[test]
fn test_signer_creation() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let signer = salter.signer(None, None, "test-path", None, true).unwrap();
assert_eq!(signer.code(), mtr_dex::ED25519_SEED);
assert!(signer.is_transferable());
let signer2 = salter.signer(None, None, "test-path", None, true).unwrap();
assert_eq!(signer.raw(), signer2.raw());
}
#[test]
fn test_signers_creation() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let signers = salter
.signers(3, 0, "test-path", None, None, None, true)
.unwrap();
assert_eq!(signers.len(), 3);
assert_ne!(signers[0].raw(), signers[1].raw());
assert_ne!(signers[1].raw(), signers[2].raw());
assert_ne!(signers[0].raw(), signers[2].raw());
}
#[test]
fn test_salter_default() {
let salter = Salter::new(None, None, None).unwrap();
assert_eq!(salter.code(), mtr_dex::SALT_128);
assert_eq!(salter.raw().len(), 16); }
#[test]
fn test_salter_with_raw() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
assert_eq!(salter.raw(), raw);
assert_eq!(salter.qb64(), "0AAwMTIzNDU2Nzg5YWJjZGVm");
}
#[test]
fn test_salter_from_qb64() {
let qb64 = "0AAwMTIzNDU2Nzg5YWJjZGVm";
let salter = Salter::from_qb64b(&mut qb64.as_bytes().to_vec(), None).unwrap();
assert_eq!(salter.raw(), b"0123456789abcdef");
assert_eq!(salter.qb64(), qb64);
}
#[test]
fn test_salter_empty_qb64() {
let result = Salter::from_qb64b(&mut "".as_bytes().to_vec(), None);
assert!(result.is_err());
}
#[test]
fn test_signer_with_temp() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
assert_eq!(salter.code(), mtr_dex::SALT_128);
assert_eq!(salter.qb64(), "0AAwMTIzNDU2Nzg5YWJjZGVm");
let signer = salter.signer(None, None, "01", None, true).unwrap();
assert_eq!(signer.code(), mtr_dex::ED25519_SEED);
assert_eq!(signer.raw().len(), 32); assert_eq!(signer.verfer().code(), mtr_dex::ED25519);
assert_eq!(signer.verfer().raw().len(), 32); assert_eq!(
signer.qb64(),
"AMPsqBZxWdtYpBhrWnKYitwFa77s902Q-nX3sPTzqs0R"
);
assert_eq!(
signer.verfer().qb64(),
"DFYFwZJOMNy3FknECL8tUaQZRBUyQ9xCv6F8ckG-UCrC"
);
}
#[test]
fn test_signer_without_temp() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let signer = salter.signer(None, None, "01", None, false).unwrap();
assert_eq!(signer.code(), mtr_dex::ED25519_SEED);
assert_eq!(signer.raw().len(), 32); assert_eq!(signer.verfer().code(), mtr_dex::ED25519);
assert_eq!(signer.verfer().raw().len(), 32); assert_eq!(
signer.qb64(),
"AEkqQiNTexWB9fTLpgJp_lXW63tFlT-Y0_mgQww4o-dC"
);
assert_eq!(
signer.verfer().qb64(),
"DPJGyH9H1M_SUSf18RzX8OqdyhxEyZJpKm5Em0PnpsWd"
);
}
#[test]
fn test_stretch() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let stretched_temp = salter.stretch(32, "", Some(&Tiers::LOW), true).unwrap();
assert_eq!(
stretched_temp,
[
212, 64, 235, 166, 120, 134, 223, 147, 214, 67, 220, 184, 166, 155, 2, 175, 104,
193, 109, 40, 76, 214, 246, 134, 89, 85, 62, 36, 91, 249, 239, 192
]
);
let stretched_low = salter.stretch(32, "", Some(&Tiers::LOW), false).unwrap();
assert_eq!(
stretched_low,
[
248, 101, 128, 186, 88, 8, 185, 186, 198, 30, 132, 13, 29, 172, 167, 92, 130, 87,
99, 64, 96, 19, 253, 2, 52, 116, 140, 116, 211, 1, 25, 233
]
);
let stretched_med = salter.stretch(32, "", Some(&Tiers::MED), false).unwrap();
assert_eq!(
stretched_med,
[
44, 243, 140, 187, 233, 41, 10, 83, 81, 236, 173, 140, 57, 63, 175, 184, 176, 179,
205, 66, 218, 216, 182, 247, 13, 246, 68, 125, 90, 185, 89, 22
]
);
let stretched_high = salter.stretch(32, "", Some(&Tiers::HIGH), false).unwrap();
assert_eq!(
stretched_high,
[
40, 205, 196, 184, 53, 205, 232, 58, 252, 0, 139, 253, 166, 9, 106, 46, 121, 152,
11, 4, 28, 227, 104, 66, 99, 33, 73, 228, 57, 75, 22, 45
]
);
}
#[test]
fn test_multiple_signers() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let signers = salter
.signers(3, 0, "test-path", None, None, None, false)
.unwrap();
assert_eq!(signers.len(), 3);
for signer in &signers {
assert_eq!(signer.code(), mtr_dex::ED25519_SEED);
assert_eq!(signer.verfer().code(), mtr_dex::ED25519);
}
assert_ne!(signers[0].qb64(), signers[1].qb64());
assert_ne!(signers[0].qb64(), signers[2].qb64());
assert_ne!(signers[1].qb64(), signers[2].qb64());
}
#[test]
fn test_salter_parsable() {
let raw = b"0123456789abcdef";
let salter = Salter::new(Some(raw), None, None).unwrap();
let mut qb64b = salter.qb64b();
let parsed_salter = Salter::from_qb64b(&mut qb64b, Some(true)).unwrap();
assert_eq!(parsed_salter.raw(), raw);
assert_eq!(parsed_salter.raw(), raw);
}
}