use crate::commands::{Eip712StructDef, Eip712StructImpl, SignEip712Full};
use crate::errors::{EthAppError, EthAppResult};
use crate::types::{
Eip712ArrayLevel, Eip712Domain, Eip712Field, Eip712FieldDefinition, Eip712FieldType,
Eip712FieldValue, Eip712Struct, Eip712StructDefinition, Eip712StructImplementation,
Eip712TypedData, Eip712Types,
};
use crate::utils::validate_bip32_path;
use crate::{BipPath, Eip712Filtering, EthApp};
use async_trait::async_trait;
use ledger_sdk_transport::Exchange;
use num_bigint::{BigInt, BigUint, Sign};
use num_traits::{One, Zero};
use serde_json::{from_str, Value};
#[async_trait]
pub trait SignEip712TypedData<E>
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_typed_data(
transport: &E,
path: &BipPath,
typed_data: &Eip712TypedData,
) -> EthAppResult<crate::types::Signature, E::Error>;
async fn sign_eip712_from_json(
transport: &E,
path: &BipPath,
json_str: &str,
) -> EthAppResult<crate::types::Signature, E::Error>;
}
pub struct Eip712Converter;
impl Eip712Converter {
pub fn parse_field_type(type_str: &str) -> Result<Eip712FieldType, String> {
let type_str = type_str.trim();
if type_str.ends_with(']') {
let (base_type, array_spec) = type_str
.rsplit_once('[')
.ok_or_else(|| format!("Invalid array type format: {}", type_str))?;
let array_spec = array_spec.trim_end_matches(']');
let _array_level = if array_spec.is_empty() {
Eip712ArrayLevel::Dynamic
} else {
let size: u8 = array_spec
.parse()
.map_err(|_| format!("Invalid array size: {}", array_spec))?;
Eip712ArrayLevel::Fixed(size)
};
let base_field_type = Self::parse_base_field_type(base_type)?;
return Ok(base_field_type);
}
Self::parse_base_field_type(type_str)
}
fn parse_base_field_type(type_str: &str) -> Result<Eip712FieldType, String> {
match type_str {
"bool" => Ok(Eip712FieldType::Bool),
"address" => Ok(Eip712FieldType::Address),
"string" => Ok(Eip712FieldType::String),
"bytes" => Ok(Eip712FieldType::DynamicBytes),
_ => {
if let Some(size_str) = type_str.strip_prefix("bytes") {
if let Ok(size) = size_str.parse::<u8>() {
if size > 0 && size <= 32 {
return Ok(Eip712FieldType::FixedBytes(size));
}
}
return Err(format!("Invalid bytes size: {}", size_str));
}
if let Some(size_str) = type_str.strip_prefix("uint") {
if let Ok(size) = size_str.parse::<u16>() {
if size > 0 && size <= 256 && size % 8 == 0 {
return Ok(Eip712FieldType::Uint((size / 8) as u8));
}
}
return Err(format!("Invalid uint size: {}", size_str));
}
if let Some(size_str) = type_str.strip_prefix("int") {
if let Ok(size) = size_str.parse::<u16>() {
if size > 0 && size <= 256 && size % 8 == 0 {
return Ok(Eip712FieldType::Int((size / 8) as u8));
}
}
return Err(format!("Invalid int size: {}", size_str));
}
Ok(Eip712FieldType::Custom(type_str.to_string()))
}
}
}
pub fn convert_types_to_definitions(
types: &Eip712Types,
) -> Result<Vec<Eip712StructDefinition>, String> {
let mut definitions = Vec::new();
for (struct_name, struct_def) in types {
let mut fields = Vec::new();
for field in &struct_def.fields {
let field_type = Self::parse_field_type(&field.r#type)?;
let field_def = Eip712FieldDefinition::new(field_type, field.name.clone());
fields.push(field_def);
}
let definition = Eip712StructDefinition {
name: struct_name.clone(),
fields,
};
definitions.push(definition);
}
Ok(definitions)
}
pub fn convert_value_to_field_value(
value: &Value,
field_type: &Eip712FieldType,
) -> Result<Eip712FieldValue, String> {
match field_type {
Eip712FieldType::Bool => {
let bool_val = value
.as_bool()
.ok_or_else(|| "Expected boolean value".to_string())?;
Ok(Eip712FieldValue::from_bool(bool_val))
}
Eip712FieldType::Address => {
let addr_str = value
.as_str()
.ok_or_else(|| "Expected string value for address".to_string())?;
Eip712FieldValue::from_address_string(addr_str)
}
Eip712FieldType::String => {
let str_val = value
.as_str()
.ok_or_else(|| "Expected string value".to_string())?;
Ok(Eip712FieldValue::from_string(str_val))
}
Eip712FieldType::Uint(size) => {
let bytes = Self::parse_uint_to_min_be(value, *size)?;
Ok(Eip712FieldValue::from_bytes(bytes))
}
Eip712FieldType::Int(size) => {
let bytes = Self::parse_int_to_min_be(value, *size)?;
Ok(Eip712FieldValue::from_bytes(bytes))
}
Eip712FieldType::FixedBytes(size) => {
let hex_str = value
.as_str()
.ok_or_else(|| "Expected hex string for bytes".to_string())?;
let bytes = hex::decode(hex_str.trim_start_matches("0x"))
.map_err(|e| format!("Invalid hex string: {}", e))?;
if bytes.len() != *size as usize {
return Err(format!("Expected {} bytes, got {}", size, bytes.len()));
}
Ok(Eip712FieldValue::from_bytes(bytes))
}
Eip712FieldType::DynamicBytes => {
let hex_str = value
.as_str()
.ok_or_else(|| "Expected hex string for bytes".to_string())?;
let bytes = hex::decode(hex_str.trim_start_matches("0x"))
.map_err(|e| format!("Invalid hex string: {}", e))?;
Ok(Eip712FieldValue::from_bytes(bytes))
}
Eip712FieldType::Custom(_) => {
Ok(Eip712FieldValue::from_struct())
}
}
}
fn parse_uint_to_min_be(value: &Value, size_bytes: u8) -> Result<Vec<u8>, String> {
let bits: u32 = (size_bytes as u32) * 8;
let big: BigUint = if let Some(u) = value.as_u64() {
BigUint::from(u)
} else if let Some(s) = value.as_str() {
let s = s.trim();
if s.starts_with("0x") || s.starts_with("0X") {
let hex_str = &s[2..];
let bytes = hex::decode(hex_str)
.map_err(|e| format!("Invalid hex for uint{}: {}", bits, e))?;
BigUint::from_bytes_be(&bytes)
} else {
BigUint::parse_bytes(s.as_bytes(), 10)
.ok_or_else(|| format!("Invalid decimal string for uint{}", bits))?
}
} else {
return Err(format!(
"Expected number or numeric string for uint{}",
bits
));
};
let max = BigUint::one() << bits;
if big >= max {
return Err(format!("uint{} value out of range", bits));
}
if big.is_zero() {
return Ok(vec![0u8]);
}
let mut out = big.to_bytes_be();
while out.len() > 1 && out[0] == 0 {
out.remove(0);
}
if out.len() > size_bytes as usize {
return Err(format!(
"uint{} minimal encoding exceeds {} bytes",
bits, size_bytes
));
}
Ok(out)
}
fn parse_int_to_min_be(value: &Value, size_bytes: u8) -> Result<Vec<u8>, String> {
let bits: u32 = (size_bytes as u32) * 8;
let big: BigInt = if let Some(i) = value.as_i64() {
BigInt::from(i)
} else if let Some(s) = value.as_str() {
let s = s.trim();
if s.starts_with("-0x") || s.starts_with("-0X") {
let hex_str = &s[3..];
let bytes = hex::decode(hex_str)
.map_err(|e| format!("Invalid hex for int{}: {}", bits, e))?;
-BigInt::from(BigUint::from_bytes_be(&bytes))
} else if s.starts_with("0x") || s.starts_with("0X") {
let hex_str = &s[2..];
let bytes = hex::decode(hex_str)
.map_err(|e| format!("Invalid hex for int{}: {}", bits, e))?;
BigInt::from(BigUint::from_bytes_be(&bytes))
} else {
BigInt::parse_bytes(s.as_bytes(), 10)
.ok_or_else(|| format!("Invalid decimal string for int{}", bits))?
}
} else {
return Err(format!("Expected number or numeric string for int{}", bits));
};
let one = BigUint::one();
let max_pos = (one.clone() << (bits - 1)) - one.clone();
let min_neg = -BigInt::from(one.clone() << (bits - 1));
if big < min_neg || big > BigInt::from(max_pos.clone()) {
return Err(format!("int{} value out of range", bits));
}
let modulus = one << bits;
let as_uint = if big.sign() == Sign::Minus {
let abs = (-&big).to_biguint().unwrap();
(&modulus - abs) % &modulus
} else {
big.to_biguint().unwrap()
};
let mut full = as_uint.to_bytes_be();
if full.is_empty() {
full.push(0);
}
if full.len() > size_bytes as usize {
return Err(format!(
"int{} minimal encoding exceeds {} bytes",
bits, size_bytes
));
}
if big.sign() == Sign::Minus {
while full.len() > 1 && full[0] == 0xFF && (full[1] & 0x80) == 0x80 {
full.remove(0);
}
} else {
while full.len() > 1 && full[0] == 0x00 && (full[1] & 0x80) == 0x00 {
full.remove(0);
}
}
Ok(full)
}
pub fn convert_message_to_implementation(
message: &Value,
primary_type: &str,
types: &Eip712Types,
) -> Result<Eip712StructImplementation, String> {
let struct_def = types
.get(primary_type)
.ok_or_else(|| format!("Primary type '{}' not found in types", primary_type))?;
let mut values = Vec::new();
for field in &struct_def.fields {
let field_value = message
.get(&field.name)
.ok_or_else(|| format!("Field '{}' not found in message", field.name))?;
let field_type = Self::parse_field_type(&field.r#type)?;
let field_val = Self::convert_value_to_field_value(field_value, &field_type)?;
values.push(field_val);
}
Ok(Eip712StructImplementation {
name: primary_type.to_string(),
values,
})
}
pub fn parse_json_to_typed_data(json_str: &str) -> Result<Eip712TypedData, String> {
let json_value: Value =
from_str(json_str).map_err(|e| format!("Invalid JSON format: {}", e))?;
if !json_value.is_object() {
return Err("JSON must be an object".to_string());
}
let obj = json_value.as_object().unwrap();
let domain_value = obj
.get("domain")
.ok_or_else(|| "Missing 'domain' field".to_string())?;
let domain: Eip712Domain = Self::parse_domain(domain_value)?;
let types_value = obj
.get("types")
.ok_or_else(|| "Missing 'types' field".to_string())?;
let types = Self::parse_types(types_value)?;
let primary_type: String = obj
.get("primaryType")
.ok_or_else(|| "Missing 'primaryType' field".to_string())?
.as_str()
.ok_or_else(|| "primaryType must be a string".to_string())?
.to_string();
let message = obj
.get("message")
.ok_or_else(|| "Missing 'message' field".to_string())?
.clone();
if !types.contains_key(&primary_type) {
return Err(format!(
"Primary type '{}' not found in types",
primary_type
));
}
Ok(Eip712TypedData::new(domain, types, primary_type, message))
}
fn parse_domain(domain_value: &Value) -> Result<Eip712Domain, String> {
if !domain_value.is_object() {
return Err("Domain must be an object".to_string());
}
let domain_obj = domain_value.as_object().unwrap();
let mut domain = Eip712Domain::new();
if let Some(name) = domain_obj.get("name") {
if let Some(name_str) = name.as_str() {
domain = domain.with_name(name_str.to_string());
}
}
if let Some(version) = domain_obj.get("version") {
if let Some(version_str) = version.as_str() {
domain = domain.with_version(version_str.to_string());
}
}
if let Some(chain_id) = domain_obj.get("chainId") {
if let Some(chain_id_num) = chain_id.as_u64() {
domain = domain.with_chain_id(chain_id_num);
}
}
if let Some(verifying_contract) = domain_obj.get("verifyingContract") {
if let Some(contract_str) = verifying_contract.as_str() {
domain = domain.with_verifying_contract(contract_str.to_string());
}
}
if let Some(salt) = domain_obj.get("salt") {
if let Some(salt_str) = salt.as_str() {
let salt_bytes = hex::decode(salt_str.trim_start_matches("0x"))
.map_err(|e| format!("Invalid salt hex: {}", e))?;
domain = domain.with_salt(salt_bytes);
}
}
Ok(domain)
}
fn parse_types(types_value: &Value) -> Result<Eip712Types, String> {
if !types_value.is_object() {
return Err("Types must be an object".to_string());
}
let types_obj = types_value.as_object().unwrap();
let mut types = Eip712Types::new();
for (type_name, type_def) in types_obj {
if !type_def.is_array() {
return Err(format!("Type '{}' definition must be an array", type_name));
}
let fields_array = type_def.as_array().unwrap();
let mut fields = Vec::new();
for field_value in fields_array {
if !field_value.is_object() {
return Err(format!("Field in type '{}' must be an object", type_name));
}
let field_obj = field_value.as_object().unwrap();
let name = field_obj
.get("name")
.ok_or_else(|| format!("Field in type '{}' missing 'name'", type_name))?
.as_str()
.ok_or_else(|| format!("Field name in type '{}' must be a string", type_name))?
.to_string();
let field_type = field_obj
.get("type")
.ok_or_else(|| {
format!("Field '{}' in type '{}' missing 'type'", name, type_name)
})?
.as_str()
.ok_or_else(|| {
format!(
"Field type for '{}' in type '{}' must be a string",
name, type_name
)
})?
.to_string();
fields.push(Eip712Field::new(name, field_type));
}
types.insert(type_name.clone(), Eip712Struct { fields });
}
Ok(types)
}
}
#[async_trait]
impl<E> SignEip712TypedData<E> for EthApp
where
E: Exchange + Send + Sync,
E::Error: std::error::Error,
{
async fn sign_eip712_typed_data(
transport: &E,
path: &BipPath,
typed_data: &Eip712TypedData,
) -> EthAppResult<crate::types::Signature, E::Error> {
validate_bip32_path(path)?;
let struct_definitions = Eip712Converter::convert_types_to_definitions(&typed_data.types)
.map_err(EthAppError::InvalidEip712Data)?;
let mut defs_sorted = struct_definitions.clone();
defs_sorted.sort_by(|a, b| a.name.cmp(&b.name));
for struct_def in &defs_sorted {
EthApp::send_struct_definition(transport, struct_def).await?;
}
let mut domain_values: Vec<Eip712FieldValue> = Vec::new();
if let Some(name) = &typed_data.domain.name {
domain_values.push(Eip712FieldValue::from_string(name));
}
if let Some(version) = &typed_data.domain.version {
domain_values.push(Eip712FieldValue::from_string(version));
}
if let Some(chain_id) = typed_data.domain.chain_id {
let chain_id_val = serde_json::Value::Number(chain_id.into());
let bytes = Eip712Converter::parse_uint_to_min_be(&chain_id_val, 32)
.map_err(EthAppError::InvalidEip712Data)?;
domain_values.push(Eip712FieldValue::from_bytes(bytes));
}
if let Some(addr) = &typed_data.domain.verifying_contract {
let addr_val = Eip712FieldValue::from_address_string(addr)
.map_err(EthAppError::InvalidEip712Data)?;
domain_values.push(addr_val);
}
let domain_impl = Eip712StructImplementation {
name: "EIP712Domain".to_string(),
values: domain_values,
};
EthApp::activate_filtering(transport).await?;
EthApp::send_struct_implementation(transport, &domain_impl).await?;
let struct_implementation = Eip712Converter::convert_message_to_implementation(
&typed_data.message,
&typed_data.primary_type,
&typed_data.types,
)
.map_err(EthAppError::InvalidEip712Data)?;
EthApp::send_struct_implementation(transport, &struct_implementation).await?;
EthApp::sign_eip712_full(transport, path).await
}
async fn sign_eip712_from_json(
transport: &E,
path: &BipPath,
json_str: &str,
) -> EthAppResult<crate::types::Signature, E::Error> {
let typed_data = Eip712Converter::parse_json_to_typed_data(json_str)
.map_err(EthAppError::InvalidEip712Data)?;
println!("typed_data: {:?}", &typed_data);
Self::sign_eip712_typed_data(transport, path, &typed_data).await
}
}