# Copyright (c) 2016-2023 The Ruby-Eth Contributors## Licensed under the Apache License, Version 2.0 (the "License");# you may not use this file except in compliance with the License.# You may obtain a copy of the License at## http://www.apache.org/licenses/LICENSE-2.0## Unless required by applicable law or agreed to in writing, software# distributed under the License is distributed on an "AS IS" BASIS,# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.# See the License for the specific language governing permissions and# limitations under the License.# Provides the {Eth} module.moduleEth# Defines handy tools for encoding typed structured data as per EIP-712.# Ref: https://eips.ethereum.org/EIPS/eip-712moduleEip712extendself# Provides a special typed-data error if data structure fails basic# verification.classTypedDataError<StandardError;end# Scans all dependencies of a given type recursively and returns# either all dependencies or none if not found.## @param primary_type [String] the primary type which we want to scan.# @param types [Array] all existing types in the data structure.# @param result [Array] found results from previous recursions.# @return [Array] all dependent types for the given primary type.deftype_dependencies(primary_type,types,result=[])ifresult.include?primary_type# ignore if we already have the give type in resultsreturnresultelsiftypes[primary_type.to_sym].nil?# ignore if the type is not used, e.g., a string or address.returnresultelse# we found somethingresult.pushprimary_type# recursively look for further nested dependenciestypes[primary_type.to_sym].eachdo|t|dependency=type_dependenciest[:type],types,resultendreturnresultendend# Encode types as an EIP-712 confrom string, e.g.,# `MyType(string attribute)`.## @param primary_type [String] the type which we want to encode.# @param types [Array] all existing types in the data structure.# @return [String] an EIP-712 encoded type-string.# @raise [TypedDataError] if non-primary type found.defencode_type(primary_type,types)# get all used typesall_dependencies=type_dependenciesprimary_type,types# remove primary types and sort the rest alphabeticallyfiltered_dependencies=all_dependencies.delete_if{|type|type.to_s==primary_type}sorted_dependencies=filtered_dependencies.sortdependencies=[primary_type]sorted_dependencies.eachdo|sorted|dependencies.pushsortedend# join them all in a string with types and field namesresult=""dependencies.eachdo|type|# dependencies should not have non-primary types (such as string, address)raiseTypedDataError,"Non-primary type found: #{type}!"iftypes[type.to_sym].nil?result+="#{type}("result+=types[type.to_sym].map{|t|"#{t[:type]}#{t[:name]}"}.join(",")result+=")"endreturnresultend# Hashes an EIP-712 confrom type-string.## @param primary_type [String] the type which we want to hash.# @param types [Array] all existing types in the data structure.# @return [String] a Keccak-256 hash of an EIP-712 encoded type-string.defhash_type(primary_type,types)encoded_type=encode_typeprimary_type,typesreturnUtil.keccak256encoded_typeend# Recursively ABI-encodes all data and types according to EIP-712.## @param primary_type [String] the primary type which we want to encode.# @param data [Array] the data in the data structure we want to encode.# @param types [Array] all existing types in the data structure.# @return [String] an ABI-encoded representation of the data and the types.defencode_data(primary_type,data,types)# first data field is the type hashencoded_types=["bytes32"]encoded_values=[hash_type(primary_type,types)]# adds field contentstypes[primary_type.to_sym].eachdo|field|value=data[field[:name].to_sym]type=field[:type]raiseNotImplementedError,"Arrays currently unimplemented for EIP-712."iftype.end_with?"]"iftype=="string"ortype=="bytes"encoded_types.push"bytes32"encoded_values.pushUtil.keccak256valueelsif!types[type.to_sym].nil?encoded_types.push"bytes32"value=encode_datatype,value,typesencoded_values.pushUtil.keccak256valueelseencoded_types.pushtypeencoded_values.pushvalueendend# all data is abi-encodedreturnAbi.encodeencoded_types,encoded_valuesend# Recursively ABI-encodes and hashes all data and types.## @param primary_type [String] the primary type which we want to hash.# @param data [Array] the data in the data structure we want to hash.# @param types [Array] all existing types in the data structure.# @return [String] a Keccak-256 hash of the ABI-encoded data and types.defhash_data(primary_type,data,types)encoded_data=encode_dataprimary_type,data,typesreturnUtil.keccak256encoded_dataend# Enforces basic properties to be represented in the EIP-712 typed# data structure: types, domain, message, etc.## @param data [Array] the data in the data structure we want to hash.# @return [Array] the data in the data structure we want to hash.# @raise [TypedDataError] if the data fails validation.defenforce_typed_data(data)data=JSON.parsedataifUtil.hex?dataraiseTypedDataError,"Data is missing, try again with data."ifdata.nil?ordata.empty?raiseTypedDataError,"Data types are missing."ifdata[:types].nil?ordata[:types].empty?raiseTypedDataError,"Data primaryType is missing."ifdata[:primaryType].nil?ordata[:primaryType].empty?raiseTypedDataError,"Data domain is missing."ifdata[:domain].nil?raiseTypedDataError,"Data message is missing."ifdata[:message].nil?ordata[:message].empty?raiseTypedDataError,"Data EIP712Domain is missing."ifdata[:types][:EIP712Domain].nil?returndataend# Hashes a typed data structure with Keccak-256 to prepare a signed# typed data operation respecting EIP-712.## @param data [Array] all the data in the typed data structure.# @return [String] a Keccak-256 hash of the EIP-712-encoded typed data.defhash(data)data=enforce_typed_datadata# EIP-191 prefix bytebuffer=Signature::EIP191_PREFIX_BYTE# EIP-712 version bytebuffer+=Signature::EIP712_VERSION_BYTE# hashed domain databuffer+=hash_data"EIP712Domain",data[:domain],data[:types]# hashed message databuffer+=hash_datadata[:primaryType],data[:message],data[:types]returnUtil.keccak256bufferendendend