module Eth::Eip712

def encode_data(primary_type, data, types)

Returns:
  • (String) - an ABI-encoded representation of the data and the types.

Parameters:
  • types (Array) -- all existing types in the data structure.
  • data (Array) -- the data in the data structure we want to encode.
  • primary_type (String) -- the primary type which we want to encode.
def encode_data(primary_type, data, types)
  # first data field is the type hash
  encoded_types = ["bytes32"]
  encoded_values = [hash_type(primary_type, types)]
  # adds field contents
  types[primary_type.to_sym].each do |field|
    value = data[field[:name].to_sym]
    type = field[:type]
    raise NotImplementedError, "Arrays currently unimplemented for EIP-712." if type.end_with? "]"
    if type == "string" or type == "bytes"
      encoded_types.push "bytes32"
      encoded_values.push Util.keccak256 value
    elsif !types[type.to_sym].nil?
      encoded_types.push "bytes32"
      value = encode_data type, value, types
      encoded_values.push Util.keccak256 value
    else
      encoded_types.push type
      encoded_values.push value
    end
  end
  # all data is abi-encoded
  return Abi.encode encoded_types, encoded_values
end

def encode_type(primary_type, types)

Raises:
  • (TypedDataError) - if non-primary type found.

Returns:
  • (String) - an EIP-712 encoded type-string.

Parameters:
  • types (Array) -- all existing types in the data structure.
  • primary_type (String) -- the type which we want to encode.
def encode_type(primary_type, types)
  # get all used types
  all_dependencies = type_dependencies primary_type, types
  # remove primary types and sort the rest alphabetically
  filtered_dependencies = all_dependencies.delete_if { |type| type.to_s == primary_type }
  sorted_dependencies = filtered_dependencies.sort
  dependencies = [primary_type]
  sorted_dependencies.each do |sorted|
    dependencies.push sorted
  end
  # join them all in a string with types and field names
  result = ""
  dependencies.each do |type|
    # dependencies should not have non-primary types (such as string, address)
    raise TypedDataError, "Non-primary type found: #{type}!" if types[type.to_sym].nil?
    result += "#{type}("
    result += types[type.to_sym].map { |t| "#{t[:type]} #{t[:name]}" }.join(",")
    result += ")"
  end
  return result
end

def enforce_typed_data(data)

Raises:
  • (TypedDataError) - if the data fails validation.

Returns:
  • (Array) - the data in the data structure we want to hash.

Parameters:
  • data (Array) -- the data in the data structure we want to hash.
def enforce_typed_data(data)
  data = JSON.parse data if Util.hex? data
  raise TypedDataError, "Data is missing, try again with data." if data.nil? or data.empty?
  raise TypedDataError, "Data types are missing." if data[:types].nil? or data[:types].empty?
  raise TypedDataError, "Data primaryType is missing." if data[:primaryType].nil? or data[:primaryType].empty?
  raise TypedDataError, "Data domain is missing." if data[:domain].nil?
  raise TypedDataError, "Data message is missing." if data[:message].nil? or data[:message].empty?
  raise TypedDataError, "Data EIP712Domain is missing." if data[:types][:EIP712Domain].nil?
  return data
end

def hash(data)

Returns:
  • (String) - a Keccak-256 hash of the EIP-712-encoded typed data.

Parameters:
  • data (Array) -- all the data in the typed data structure.
def hash(data)
  data = enforce_typed_data data
  # EIP-191 prefix byte
  buffer = Signature::EIP191_PREFIX_BYTE
  # EIP-712 version byte
  buffer += Signature::EIP712_VERSION_BYTE
  # hashed domain data
  buffer += hash_data "EIP712Domain", data[:domain], data[:types]
  # hashed message data
  buffer += hash_data data[:primaryType], data[:message], data[:types]
  return Util.keccak256 buffer
end

def hash_data(primary_type, data, types)

Returns:
  • (String) - a Keccak-256 hash of the ABI-encoded data and types.

Parameters:
  • types (Array) -- all existing types in the data structure.
  • data (Array) -- the data in the data structure we want to hash.
  • primary_type (String) -- the primary type which we want to hash.
def hash_data(primary_type, data, types)
  encoded_data = encode_data primary_type, data, types
  return Util.keccak256 encoded_data
end

def hash_type(primary_type, types)

Returns:
  • (String) - a Keccak-256 hash of an EIP-712 encoded type-string.

Parameters:
  • types (Array) -- all existing types in the data structure.
  • primary_type (String) -- the type which we want to hash.
def hash_type(primary_type, types)
  encoded_type = encode_type primary_type, types
  return Util.keccak256 encoded_type
end

def type_dependencies(primary_type, types, result = [])

Returns:
  • (Array) - all dependent types for the given primary type.

Parameters:
  • result (Array) -- found results from previous recursions.
  • types (Array) -- all existing types in the data structure.
  • primary_type (String) -- the primary type which we want to scan.
def type_dependencies(primary_type, types, result = [])
  if result.include? primary_type
    # ignore if we already have the give type in results
    return result
  elsif types[primary_type.to_sym].nil?
    # ignore if the type is not used, e.g., a string or address.
    return result
  else
    # we found something
    result.push primary_type
    # recursively look for further nested dependencies
    types[primary_type.to_sym].each do |t|
      dependency = type_dependencies t[:type], types, result
    end
    return result
  end
end