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, length, p1_sign_eip712, p2_sign_eip712};
use crate::types::{BipPath, SignEip712Params, Signature};
use crate::utils::{encode_bip32_path, validate_bip32_path};
use crate::EthApp;
pub fn parse_signature_response<E: std::error::Error>(data: &[u8]) -> EthAppResult<Signature, E> {
if data.len() != 65 {
return Err(EthAppError::InvalidResponseData(format!(
"Invalid signature response length: {} bytes (expected 65)",
data.len()
)));
}
let v = data[0];
let r = data[1..33].to_vec();
let s = data[33..65].to_vec();
Signature::new(v, r, s).map_err(|e| EthAppError::InvalidSignature(e))
}
#[async_trait]
pub trait SignEip712Full<E>
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_full(transport: &E, path: &BipPath) -> EthAppResult<Signature, E::Error>;
}
#[async_trait]
impl<E> SignEip712Full<E> for EthApp
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_full(transport: &E, path: &BipPath) -> EthAppResult<Signature, E::Error> {
validate_bip32_path(path)?;
let path_data = encode_bip32_path(path);
let command = APDUCommand {
cla: Self::CLA,
ins: ins::SIGN_ETH_EIP712,
p1: p1_sign_eip712::FIRST_CHUNK,
p2: p2_sign_eip712::FULL_IMPLEMENTATION,
data: path_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_signature_response::<E::Error>(response.data())
}
}
#[async_trait]
pub trait SignEip712V0<E>
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_v0(
transport: &E,
params: SignEip712Params,
) -> EthAppResult<Signature, E::Error>;
}
#[async_trait]
impl<E> SignEip712V0<E> for EthApp
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_v0(
transport: &E,
params: SignEip712Params,
) -> EthAppResult<Signature, E::Error> {
validate_bip32_path(¶ms.path)?;
if params.domain_hash.len() != length::EIP712_DOMAIN_HASH_SIZE {
return Err(EthAppError::InvalidEip712Data(format!(
"Invalid domain hash size: {} (expected {})",
params.domain_hash.len(),
length::EIP712_DOMAIN_HASH_SIZE
)));
}
if params.message_hash.len() != length::EIP712_MESSAGE_HASH_SIZE {
return Err(EthAppError::InvalidEip712Data(format!(
"Invalid message hash size: {} (expected {})",
params.message_hash.len(),
length::EIP712_MESSAGE_HASH_SIZE
)));
}
let path_data = encode_bip32_path(¶ms.path);
let mut command_data = Vec::new();
command_data.extend_from_slice(&path_data);
command_data.extend_from_slice(¶ms.domain_hash);
command_data.extend_from_slice(¶ms.message_hash);
let command = APDUCommand {
cla: Self::CLA,
ins: ins::SIGN_ETH_EIP712,
p1: p1_sign_eip712::FIRST_CHUNK,
p2: p2_sign_eip712::V0_IMPLEMENTATION,
data: command_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_signature_response::<E::Error>(response.data())
}
}