use core::fmt;
use eth_valkyoth_hash::{Keccak256, hash_one};
use eth_valkyoth_primitives::{Address, B256, ChainId};
use crate::eip712_signing_digest;
#[cfg(feature = "json")]
#[path = "eip712_json.rs"]
mod eip712_json;
#[path = "eip712_typed_helpers.rs"]
mod typed_helpers;
#[cfg(feature = "json")]
pub use eip712_json::{Eip712JsonError, Eip712JsonLimits, eip712_json_typed_data_signing_digest};
use typed_helpers::{
SliceWriter, encode_domain_type, encode_value_word, find_struct, find_value, next_dependency,
update_domain_fields, write_struct_type,
};
const WORD_BYTES: usize = 32;
const ADDRESS_PADDING_BYTES: usize = 12;
const MAX_TYPE_DEPTH: usize = 32;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Eip712StructType<'a> {
pub name: &'a str,
pub fields: &'a [Eip712Field<'a>],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Eip712Field<'a> {
pub name: &'a str,
pub type_name: &'a str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Eip712Value<'a> {
pub name: &'a str,
pub value: Eip712ValueKind<'a>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Eip712ValueKind<'a> {
Bool(bool),
Address(Address),
Uint64(u64),
Uint256([u8; 32]),
Int256([u8; 32]),
FixedBytes(&'a [u8]),
Bytes(&'a [u8]),
String(&'a str),
Struct(&'a [Eip712Value<'a>]),
Array(&'a [Eip712ValueKind<'a>]),
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Eip712DomainData<'a> {
pub name: Option<&'a str>,
pub version: Option<&'a str>,
pub chain_id: Option<ChainId>,
pub verifying_contract: Option<Address>,
pub salt: Option<B256>,
}
impl<'a> Eip712DomainData<'a> {
#[must_use]
pub const fn empty() -> Self {
Self {
name: None,
version: None,
chain_id: None,
verifying_contract: None,
salt: None,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Eip712EncodeError {
OutputTooShort,
UnknownStruct,
MissingValue,
InvalidType,
TypeMismatch,
RecursionLimit,
}
impl fmt::Display for Eip712EncodeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::OutputTooShort => "EIP-712 output buffer is too short",
Self::UnknownStruct => "EIP-712 struct type is not in the schema",
Self::MissingValue => "EIP-712 struct value is missing a field",
Self::InvalidType => "EIP-712 type string is invalid or unsupported",
Self::TypeMismatch => "EIP-712 value does not match its declared type",
Self::RecursionLimit => "EIP-712 recursion limit was exceeded",
})
}
}
#[cfg(feature = "std")]
impl std::error::Error for Eip712EncodeError {}
pub fn encode_eip712_type(
types: &[Eip712StructType<'_>],
primary_type: &str,
output: &mut [u8],
) -> Result<usize, Eip712EncodeError> {
let mut writer = SliceWriter::new(output);
let primary = find_struct(types, primary_type)?;
write_struct_type(primary, &mut writer)?;
let mut previous = None::<&str>;
loop {
let next = next_dependency(types, primary_type, previous)?;
let Some(dependency) = next else {
break;
};
write_struct_type(dependency, &mut writer)?;
previous = Some(dependency.name);
}
Ok(writer.len())
}
pub fn eip712_type_hash<H>(
types: &[Eip712StructType<'_>],
primary_type: &str,
scratch: &mut [u8],
) -> Result<B256, Eip712EncodeError>
where
H: Default + Keccak256,
{
let len = encode_eip712_type(types, primary_type, scratch)?;
let encoded = scratch
.get(..len)
.ok_or(Eip712EncodeError::OutputTooShort)?;
Ok(hash_one(H::default(), encoded))
}
pub fn encode_eip712_data<H>(
types: &[Eip712StructType<'_>],
primary_type: &str,
values: &[Eip712Value<'_>],
output: &mut [u8],
type_scratch: &mut [u8],
) -> Result<usize, Eip712EncodeError>
where
H: Default + Keccak256,
{
let ty = find_struct(types, primary_type)?;
let len = ty
.fields
.len()
.checked_mul(WORD_BYTES)
.ok_or(Eip712EncodeError::OutputTooShort)?;
let target = output
.get_mut(..len)
.ok_or(Eip712EncodeError::OutputTooShort)?;
for (field, slot) in ty.fields.iter().zip(target.chunks_exact_mut(WORD_BYTES)) {
let value = find_value(values, field.name)?;
let mut word = [0_u8; WORD_BYTES];
encode_value_word::<H>(types, field.type_name, value, &mut word, type_scratch, 0)?;
slot.copy_from_slice(&word);
}
Ok(len)
}
pub fn eip712_hash_struct<H>(
types: &[Eip712StructType<'_>],
primary_type: &str,
values: &[Eip712Value<'_>],
type_scratch: &mut [u8],
) -> Result<B256, Eip712EncodeError>
where
H: Default + Keccak256,
{
hash_struct_inner::<H>(types, primary_type, values, type_scratch, 0)
}
pub fn eip712_domain_separator<H>(
domain: Eip712DomainData<'_>,
type_scratch: &mut [u8],
) -> Result<B256, Eip712EncodeError>
where
H: Default + Keccak256,
{
let type_len = encode_domain_type(domain, type_scratch)?;
let type_bytes = type_scratch
.get(..type_len)
.ok_or(Eip712EncodeError::OutputTooShort)?;
let type_hash = hash_one(H::default(), type_bytes);
let mut hasher = H::default();
hasher.update(&type_hash.to_bytes());
update_domain_fields::<H>(domain, &mut hasher);
Ok(hasher.finalize())
}
pub fn eip712_typed_data_signing_digest<H>(
domain: Eip712DomainData<'_>,
types: &[Eip712StructType<'_>],
primary_type: &str,
values: &[Eip712Value<'_>],
type_scratch: &mut [u8],
) -> Result<B256, Eip712EncodeError>
where
H: Default + Keccak256,
{
let domain_separator = eip712_domain_separator::<H>(domain, type_scratch)?;
let message_hash = eip712_hash_struct::<H>(types, primary_type, values, type_scratch)?;
Ok(eip712_signing_digest(
domain_separator,
message_hash,
H::default(),
))
}
fn hash_struct_inner<H>(
types: &[Eip712StructType<'_>],
primary_type: &str,
values: &[Eip712Value<'_>],
type_scratch: &mut [u8],
depth: usize,
) -> Result<B256, Eip712EncodeError>
where
H: Default + Keccak256,
{
if depth >= MAX_TYPE_DEPTH {
return Err(Eip712EncodeError::RecursionLimit);
}
let ty = find_struct(types, primary_type)?;
let type_hash = eip712_type_hash::<H>(types, primary_type, type_scratch)?;
let mut hasher = H::default();
hasher.update(&type_hash.to_bytes());
for field in ty.fields {
let value = find_value(values, field.name)?;
let mut word = [0_u8; WORD_BYTES];
encode_value_word::<H>(
types,
field.type_name,
value,
&mut word,
type_scratch,
depth.saturating_add(1),
)?;
hasher.update(&word);
}
Ok(hasher.finalize())
}
#[cfg(test)]
#[path = "eip712_typed_tests.rs"]
mod tests;