use crate::errors::{EthAppError, EthAppResult};
use crate::instructions::length;
use crate::types::{BipPath, EthAddress};
pub fn encode_bip32_path(path: &BipPath) -> Vec<u8> {
let mut encoded = Vec::new();
encoded.push(path.indices.len() as u8);
for &index in &path.indices {
encoded.extend_from_slice(&index.to_be_bytes());
}
encoded
}
pub fn decode_bip32_path<E: std::error::Error>(data: &[u8]) -> EthAppResult<(BipPath, usize), E> {
if data.is_empty() {
return Err(EthAppError::InvalidBip32Path("Empty path data".to_string()));
}
let path_len = data[0] as usize;
if path_len > length::MAX_BIP32_PATH_DEPTH {
return Err(EthAppError::InvalidBip32Path(format!(
"Path too deep: {} (max {})",
path_len,
length::MAX_BIP32_PATH_DEPTH
)));
}
let expected_size = 1 + path_len * length::BIP32_INDEX_SIZE;
if data.len() < expected_size {
return Err(EthAppError::InvalidBip32Path(format!(
"Insufficient data: {} bytes (expected {})",
data.len(),
expected_size
)));
}
let mut indices = Vec::with_capacity(path_len);
let mut offset = 1;
for _ in 0..path_len {
let index_bytes = &data[offset..offset + length::BIP32_INDEX_SIZE];
let index = u32::from_be_bytes([
index_bytes[0],
index_bytes[1],
index_bytes[2],
index_bytes[3],
]);
indices.push(index);
offset += length::BIP32_INDEX_SIZE;
}
let path = BipPath::new(indices).map_err(|e| EthAppError::InvalidBip32Path(e))?;
Ok((path, offset))
}
pub fn validate_bip32_path<E: std::error::Error>(path: &BipPath) -> EthAppResult<(), E> {
if path.indices.is_empty() {
return Err(EthAppError::InvalidBip32Path("Empty path".to_string()));
}
if path.indices.len() > length::MAX_BIP32_PATH_DEPTH {
return Err(EthAppError::InvalidBip32Path(format!(
"Path too deep: {} (max {})",
path.indices.len(),
length::MAX_BIP32_PATH_DEPTH
)));
}
if path.indices.len() >= 2 {
if path.indices.len() >= 3 && path.indices[0] == 0x8000002C && path.indices[1] == 0x8000003C
{
if (path.indices[2] & 0x80000000) == 0 {
return Err(EthAppError::InvalidBip32Path(
"Account index should be hardened for Ethereum".to_string(),
));
}
}
}
Ok(())
}
pub fn encode_chain_id(chain_id: u64) -> Vec<u8> {
chain_id.to_be_bytes().to_vec()
}
pub fn decode_chain_id<E: std::error::Error>(data: &[u8]) -> EthAppResult<u64, E> {
if data.len() < length::CHAIN_ID_SIZE {
return Err(EthAppError::InvalidResponseData(format!(
"Insufficient data for chain ID: {} bytes (expected {})",
data.len(),
length::CHAIN_ID_SIZE
)));
}
let chain_id_bytes = &data[0..length::CHAIN_ID_SIZE];
let chain_id = u64::from_be_bytes([
chain_id_bytes[0],
chain_id_bytes[1],
chain_id_bytes[2],
chain_id_bytes[3],
chain_id_bytes[4],
chain_id_bytes[5],
chain_id_bytes[6],
chain_id_bytes[7],
]);
Ok(chain_id)
}
pub fn chunk_data(data: &[u8], chunk_size: usize) -> Vec<Vec<u8>> {
if chunk_size == 0 {
return vec![data.to_vec()];
}
data.chunks(chunk_size)
.map(|chunk| chunk.to_vec())
.collect()
}
pub fn validate_ethereum_address<E: std::error::Error>(address: &str) -> EthAppResult<(), E> {
println!("validate_ethereum_address: {}", address);
if !address.starts_with("0x") {
return Err(EthAppError::InvalidAddress(
"Address must start with 0x".to_string(),
));
}
if address.len() != 42 {
return Err(EthAppError::InvalidAddress(format!(
"Address must be 42 characters long, got {}",
address.len()
)));
}
let hex_part = &address[2..];
for (i, c) in hex_part.chars().enumerate() {
if !c.is_ascii_hexdigit() {
return Err(EthAppError::InvalidAddress(format!(
"Invalid character '{}' at position {}",
c,
i + 2
)));
}
}
Ok(())
}
pub fn bytes_to_eth_address<E: std::error::Error>(bytes: &[u8]) -> EthAppResult<EthAddress, E> {
if bytes.len() != length::ETH_ADDRESS_SIZE {
return Err(EthAppError::InvalidAddress(format!(
"Invalid address length: {} bytes (expected {})",
bytes.len(),
length::ETH_ADDRESS_SIZE
)));
}
let hex_string = format!("0x{}", hex::encode(bytes));
EthAddress::new(hex_string).map_err(|e| EthAppError::InvalidAddress(e))
}
pub fn parse_device_address<E: std::error::Error>(
data: &[u8],
offset: usize,
) -> EthAppResult<(EthAddress, usize), E> {
if offset >= data.len() {
return Err(EthAppError::InvalidResponseData(
"Insufficient data for address length".to_string(),
));
}
let address_len = data[offset] as usize;
let address_start = offset + 1;
let address_end = address_start + address_len;
if address_end > data.len() {
return Err(EthAppError::InvalidResponseData(format!(
"Insufficient data for address: available {}, needed {}",
data.len() - address_start,
address_len
)));
}
let address_bytes = &data[address_start..address_end];
let mut address_str = std::str::from_utf8(address_bytes)
.map_err(|_| EthAppError::InvalidResponseData("Invalid UTF-8 in address".to_string()))?
.to_string();
if address_str.len() == 40 && !address_str.starts_with("0x") {
address_str = format!("0x{}", address_str);
}
validate_ethereum_address(&address_str)?;
let address = EthAddress::new(address_str).map_err(|e| EthAppError::InvalidAddress(e))?;
Ok((address, address_end))
}
pub fn parse_device_public_key<E: std::error::Error>(
data: &[u8],
offset: usize,
) -> EthAppResult<(Vec<u8>, usize), E> {
if offset >= data.len() {
return Err(EthAppError::InvalidResponseData(
"Insufficient data for public key length".to_string(),
));
}
let key_len = data[offset] as usize;
let key_start = offset + 1;
let key_end = key_start + key_len;
if key_end > data.len() {
return Err(EthAppError::InvalidResponseData(format!(
"Insufficient data for public key: available {}, needed {}",
data.len() - key_start,
key_len
)));
}
if key_len != 65 {
return Err(EthAppError::InvalidResponseData(format!(
"Invalid public key length: {} (expected 65)",
key_len
)));
}
let public_key = data[key_start..key_end].to_vec();
Ok((public_key, key_end))
}
pub fn parse_device_chain_code<E: std::error::Error>(
data: &[u8],
offset: usize,
) -> EthAppResult<(Option<Vec<u8>>, usize), E> {
if offset >= data.len() {
return Ok((None, offset));
}
if data.len() - offset < length::CHAIN_CODE_SIZE {
return Err(EthAppError::InvalidResponseData(format!(
"Insufficient data for chain code: available {}, needed {}",
data.len() - offset,
length::CHAIN_CODE_SIZE
)));
}
let chain_code = data[offset..offset + length::CHAIN_CODE_SIZE].to_vec();
Ok((Some(chain_code), offset + length::CHAIN_CODE_SIZE))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_bip32_path() {
let path = BipPath::new(vec![0x8000002C, 0x8000003C, 0x80000000, 0, 0]).unwrap();
let encoded = encode_bip32_path(&path);
assert_eq!(encoded[0], 5); assert_eq!(&encoded[1..5], &0x8000002Cu32.to_be_bytes());
assert_eq!(&encoded[5..9], &0x8000003Cu32.to_be_bytes());
}
#[test]
fn test_validate_ethereum_address() {
assert!(validate_ethereum_address::<std::io::Error>(
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
)
.is_ok());
assert!(validate_ethereum_address::<std::io::Error>(
"0x742d35cc6535c244b8c80a79d5d22efEAdBA5B90"
)
.is_ok());
assert!(validate_ethereum_address::<std::io::Error>(
"742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
)
.is_err());
assert!(validate_ethereum_address::<std::io::Error>(
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B9"
)
.is_err());
assert!(validate_ethereum_address::<std::io::Error>(
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B9X"
)
.is_err());
}
#[test]
fn test_parse_device_address_with_40_char_address() {
let mut response_data = Vec::new();
response_data.push(40);
response_data.extend(b"742d35Cc6535C244B8c80A79d5d22efeAdBA5B90");
let result = parse_device_address::<std::io::Error>(&response_data, 0);
assert!(result.is_ok());
let (address, offset) = result.unwrap();
assert_eq!(
address.address,
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
);
assert_eq!(offset, 41); }
#[test]
fn test_parse_device_address_with_42_char_address() {
let mut response_data = Vec::new();
response_data.push(42);
response_data.extend(b"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90");
let result = parse_device_address::<std::io::Error>(&response_data, 0);
assert!(result.is_ok());
let (address, offset) = result.unwrap();
assert_eq!(
address.address,
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
);
assert_eq!(offset, 43); }
#[test]
fn test_chunk_data() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let chunks = chunk_data(&data, 3);
assert_eq!(chunks.len(), 4);
assert_eq!(chunks[0], vec![1, 2, 3]);
assert_eq!(chunks[1], vec![4, 5, 6]);
assert_eq!(chunks[2], vec![7, 8, 9]);
assert_eq!(chunks[3], vec![10]);
}
}