use async_trait::async_trait;
use ledger_sdk_device_base::{App, AppExt};
use ledger_sdk_transport::{APDUCommand, Exchange};
use crate::errors::{EthAppError, EthAppResult};
use crate::instructions::{ins, p1_get_address, p2_get_address};
use crate::types::{GetAddressParams, PublicKeyInfo};
use crate::utils::{
encode_bip32_path, encode_chain_id, parse_device_address, parse_device_chain_code,
parse_device_public_key, validate_bip32_path,
};
use crate::EthApp;
#[async_trait]
pub trait GetAddress<E>
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn get_address(
transport: &E,
params: GetAddressParams,
) -> EthAppResult<PublicKeyInfo, E::Error>;
}
#[async_trait]
impl<E> GetAddress<E> for EthApp
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn get_address(
transport: &E,
params: GetAddressParams,
) -> EthAppResult<PublicKeyInfo, E::Error> {
validate_bip32_path(¶ms.path)?;
let mut data = Vec::new();
data.extend_from_slice(&encode_bip32_path(¶ms.path));
if let Some(chain_id) = params.chain_id {
data.extend_from_slice(&encode_chain_id(chain_id));
}
let p1 = if params.display {
p1_get_address::DISPLAY_AND_CONFIRM
} else {
p1_get_address::RETURN_ADDRESS
};
let p2 = if params.return_chain_code {
p2_get_address::RETURN_CHAIN_CODE
} else {
p2_get_address::NO_CHAIN_CODE
};
let command = APDUCommand {
cla: Self::CLA,
ins: ins::GET_ETH_PUBLIC_ADDRESS,
p1,
p2,
data,
};
let response = transport
.exchange(&command)
.await
.map_err(|e| EthAppError::Transport(e.into()))?;
<EthApp as AppExt<E>>::handle_response_error(&response).map_err(EthAppError::Transport)?;
parse_get_address_response::<E::Error>(response.data(), params.return_chain_code)
}
}
fn parse_get_address_response<E: std::error::Error>(
data: &[u8],
return_chain_code: bool,
) -> EthAppResult<PublicKeyInfo, E> {
let mut offset = 0;
let (public_key, new_offset) = parse_device_public_key(data, offset)?;
offset = new_offset;
let (address, new_offset) = parse_device_address(data, offset)?;
offset = new_offset;
let (chain_code, _) = if return_chain_code {
parse_device_chain_code(data, offset)?
} else {
(None, offset)
};
Ok(PublicKeyInfo {
public_key,
address,
chain_code,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::BipPath;
#[test]
fn test_parse_get_address_response_without_chain_code() {
let mut response_data = Vec::new();
response_data.push(65); response_data.extend(vec![0x04; 65]);
response_data.push(42); response_data.extend(b"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90");
let result = parse_get_address_response::<std::io::Error>(&response_data, false);
assert!(result.is_ok());
let public_key_info = result.unwrap();
assert_eq!(public_key_info.public_key.len(), 65);
assert_eq!(
public_key_info.address.address,
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
);
assert!(public_key_info.chain_code.is_none());
}
#[test]
fn test_parse_get_address_response_with_chain_code() {
let mut response_data = Vec::new();
response_data.push(65); response_data.extend(vec![0x04; 65]);
response_data.push(42); response_data.extend(b"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90");
response_data.extend(vec![0xAB; 32]);
let result = parse_get_address_response::<std::io::Error>(&response_data, true);
assert!(result.is_ok());
let public_key_info = result.unwrap();
assert_eq!(public_key_info.public_key.len(), 65);
assert_eq!(
public_key_info.address.address,
"0x742d35Cc6535C244B8c80A79d5d22efeAdBA5B90"
);
assert!(public_key_info.chain_code.is_some());
assert_eq!(public_key_info.chain_code.unwrap().len(), 32);
}
#[test]
fn test_get_address_params() {
let path = BipPath::ethereum_standard(0, 0);
let params = GetAddressParams::new(path.clone())
.with_display()
.with_chain_code()
.with_chain_id(1);
assert_eq!(params.path, path);
assert!(params.display);
assert!(params.return_chain_code);
assert_eq!(params.chain_id, Some(1));
}
}