use crate::crypto::constants::{HashType, KeyType, GOVERNANCE_PREFIX, SMART_CONTRACT_PREFIX};
use crate::crypto::hash::create_hash_chain;
use crate::error::{Result, ZeraError};
pub fn generate_zera_address(
public_key: &[u8],
_key_type: KeyType,
hash_types: &[HashType],
) -> Result<String> {
if public_key.is_empty() {
return Err(ZeraError::InvalidInput(
"Public key must be non-empty".into(),
));
}
if hash_types.is_empty() {
return Ok(bs58::encode(public_key).into_string());
}
let hashed = create_hash_chain(hash_types, public_key);
Ok(bs58::encode(hashed).into_string())
}
pub fn generate_address_from_public_key(public_key_identifier: &str) -> Result<String> {
if public_key_identifier.is_empty() {
return Err(ZeraError::InvalidInput(
"Public key identifier must be a non-empty string".into(),
));
}
let last_underscore = public_key_identifier.rfind('_').ok_or_else(|| {
ZeraError::Validation("Invalid public key identifier: no underscore found".into())
})?;
let base58_part = &public_key_identifier[last_underscore + 1..];
if base58_part.is_empty() {
return Err(ZeraError::Validation(
"Invalid public key identifier: nothing after last underscore".into(),
));
}
let public_key_bytes = bs58::decode(base58_part).into_vec().map_err(|e| {
ZeraError::Validation(format!(
"Invalid public key identifier: failed to decode base58 part - {e}"
))
})?;
let hash_types = get_hash_types_from_public_key(public_key_identifier)?;
if hash_types.is_empty() {
return Ok(bs58::encode(public_key_bytes).into_string());
}
let hashed = create_hash_chain(&hash_types, &public_key_bytes);
Ok(bs58::encode(hashed).into_string())
}
pub fn get_address_from_public_key(public_key_identifier: &str) -> Result<String> {
generate_address_from_public_key(public_key_identifier)
}
pub fn is_valid_public_key_identifier(public_key_identifier: &str) -> bool {
generate_address_from_public_key(public_key_identifier).is_ok()
}
pub fn get_key_type_from_public_key(public_key_identifier: &str) -> Result<KeyType> {
if public_key_identifier.is_empty() {
return Err(ZeraError::InvalidInput(
"Public key identifier must be a non-empty string".into(),
));
}
if public_key_identifier.starts_with(SMART_CONTRACT_PREFIX)
|| public_key_identifier.starts_with(GOVERNANCE_PREFIX)
{
return Err(ZeraError::Validation(
"Special identifiers (sc_, gov_) do not have key types".into(),
));
}
let mut identifier = public_key_identifier;
if let Some(stripped) = identifier.strip_prefix("r_") {
identifier = stripped;
}
if identifier.starts_with("A") {
Ok(KeyType::Ed25519)
} else if identifier.starts_with("B") {
Ok(KeyType::Ed448)
} else {
Err(ZeraError::Validation(
"Invalid public key identifier: missing key type prefix (A or B)".into(),
))
}
}
pub fn get_public_key_identifier_from_bytes(bytes: &[u8]) -> Result<String> {
if bytes.is_empty() {
return Err(ZeraError::InvalidInput(
"Bytes must be a non-empty slice".into(),
));
}
if bytes.len() >= 3 {
let first3 = String::from_utf8_lossy(&bytes[0..3]);
if first3 == "sc_" || first3 == "gov" {
return String::from_utf8(bytes.to_vec()).map_err(|e| {
ZeraError::Validation(format!("Invalid UTF-8 for special identifier: {e}"))
});
}
}
let mut last_underscore_index: Option<usize> = None;
for (i, byte) in bytes.iter().copied().enumerate() {
if byte == b'_' {
last_underscore_index = Some(i);
}
let valid_prefix_byte = (b'A'..=b'B').contains(&byte)
|| (b'a'..=b'c').contains(&byte)
|| byte == b'_'
|| byte == b'r';
if !valid_prefix_byte {
break;
}
}
let last_underscore_index = last_underscore_index.ok_or_else(|| {
ZeraError::Validation("Invalid public key bytes: no underscore found in prefix".into())
})?;
let actual_prefix_end = last_underscore_index + 1;
let prefix = String::from_utf8(bytes[..actual_prefix_end].to_vec())
.map_err(|e| ZeraError::Validation(format!("Invalid public key prefix bytes: {e}")))?;
let raw_bytes = &bytes[actual_prefix_end..];
if raw_bytes.is_empty() {
return Err(ZeraError::Validation(
"Invalid public key bytes: no data after prefix".into(),
));
}
let b58 = bs58::encode(raw_bytes).into_string();
Ok(format!("{prefix}{b58}"))
}
pub fn get_public_key_bytes(public_key_identifier: &str) -> Result<Vec<u8>> {
if public_key_identifier.is_empty() {
return Err(ZeraError::InvalidInput(
"Public key identifier must be a non-empty string".into(),
));
}
if public_key_identifier.starts_with(SMART_CONTRACT_PREFIX)
|| public_key_identifier.starts_with(GOVERNANCE_PREFIX)
{
return Ok(public_key_identifier.as_bytes().to_vec());
}
let last_underscore = public_key_identifier.rfind('_').ok_or_else(|| {
ZeraError::Validation("Invalid public key identifier: no underscore found".into())
})?;
let prefix = &public_key_identifier[..=last_underscore];
let base58_part = &public_key_identifier[last_underscore + 1..];
if base58_part.is_empty() {
return Err(ZeraError::Validation(
"Invalid public key identifier: nothing after last underscore".into(),
));
}
let decoded = bs58::decode(base58_part).into_vec().map_err(|e| {
ZeraError::Validation(format!(
"Invalid public key identifier: failed to decode base58 part - {e}"
))
})?;
let mut result = prefix.as_bytes().to_vec();
result.extend(decoded);
Ok(result)
}
pub fn get_hash_types_from_public_key(public_key_identifier: &str) -> Result<Vec<HashType>> {
if public_key_identifier.is_empty() {
return Err(ZeraError::InvalidInput(
"Public key identifier must be a non-empty string".into(),
));
}
if public_key_identifier.starts_with(SMART_CONTRACT_PREFIX)
|| public_key_identifier.starts_with(GOVERNANCE_PREFIX)
{
return Err(ZeraError::Validation(
"Special identifiers (sc_, gov_) do not have hash types".into(),
));
}
let mut remaining = public_key_identifier;
if let Some(stripped) = remaining.strip_prefix("r_") {
remaining = stripped;
}
if remaining.starts_with("A") || remaining.starts_with("B") {
remaining = &remaining[2..];
if let Some(stripped) = remaining.strip_prefix('_') {
remaining = stripped;
}
} else {
return Err(ZeraError::Validation(
"Invalid public key identifier: missing key type prefix (A or B)".into(),
));
}
let mut hash_types = Vec::new();
loop {
if remaining.starts_with("a") {
hash_types.push(HashType::Sha3_256);
remaining = &remaining[2..];
} else if remaining.starts_with("b") {
hash_types.push(HashType::Sha3_512);
remaining = &remaining[2..];
} else if remaining.starts_with("c") {
hash_types.push(HashType::Blake3);
remaining = &remaining[2..];
} else {
break;
}
if let Some(stripped) = remaining.strip_prefix('_') {
remaining = stripped;
}
}
Ok(hash_types)
}
pub fn sanitize_and_decode_address(address: &str) -> Result<Vec<u8>> {
if address.is_empty() {
return Err(ZeraError::InvalidInput(
"Address must be a non-empty string".into(),
));
}
let sanitized = address.trim();
if sanitized.is_empty() {
return Err(ZeraError::InvalidInput("Address cannot be empty".into()));
}
bs58::decode(sanitized)
.into_vec()
.map_err(|e| ZeraError::Validation(format!("Invalid address format: {e}")))
}