Module: Klay::Eip712
Overview
Defines handy tools for encoding typed structured data as per EIP-712. Ref: https://eips.ethereum.org/EIPS/eip-712
Defined Under Namespace
Classes: TypedDataError
Class Method Summary collapse
-
.encode_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes all data and types according to EIP-712.
-
.encode_type(primary_type, types) ⇒ String
Encode types as an EIP-712 confrom string, e.g.,
MyType(string attribute)
. -
.enforce_typed_data(data) ⇒ Array
Enforces basic properties to be represented in the EIP-712 typed data structure: types, domain, message, etc.
-
.hash(data) ⇒ String
Hashes a typed data structure with Keccak-256 to prepare a signed typed data operation respecting EIP-712.
-
.hash_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes and hashes all data and types.
-
.hash_type(primary_type, types) ⇒ String
Hashes an EIP-712 confrom type-string.
-
.type_dependencies(primary_type, types, result = []) ⇒ Array
Scans all dependencies of a given type recursively and returns either all dependencies or none if not found.
Instance Method Summary collapse
-
#encode_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes all data and types according to EIP-712.
-
#encode_type(primary_type, types) ⇒ String
Encode types as an EIP-712 confrom string, e.g.,
MyType(string attribute)
. -
#enforce_typed_data(data) ⇒ Array
Enforces basic properties to be represented in the EIP-712 typed data structure: types, domain, message, etc.
-
#hash(data) ⇒ String
Hashes a typed data structure with Keccak-256 to prepare a signed typed data operation respecting EIP-712.
-
#hash_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes and hashes all data and types.
-
#hash_type(primary_type, types) ⇒ String
Hashes an EIP-712 confrom type-string.
-
#type_dependencies(primary_type, types, result = []) ⇒ Array
Scans all dependencies of a given type recursively and returns either all dependencies or none if not found.
Class Method Details
.encode_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes all data and types according to EIP-712.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/klay/eip712.rb', line 106 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 |
.encode_type(primary_type, types) ⇒ String
Encode types as an EIP-712 confrom string, e.g.,
MyType(string attribute)
.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/klay/eip712.rb', line 63 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 |
.enforce_typed_data(data) ⇒ Array
Enforces basic properties to be represented in the EIP-712 typed data structure: types, domain, message, etc.
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/klay/eip712.rb', line 151 def enforce_typed_data(data) data = JSON.parse data if Util.is_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 |
.hash(data) ⇒ String
Hashes a typed data structure with Keccak-256 to prepare a signed typed data operation respecting EIP-712.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/klay/eip712.rb', line 167 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 |
.hash_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes and hashes all data and types.
140 141 142 143 |
# File 'lib/klay/eip712.rb', line 140 def hash_data(primary_type, data, types) encoded_data = encode_data primary_type, data, types return Util.keccak256 encoded_data end |
.hash_type(primary_type, types) ⇒ String
Hashes an EIP-712 confrom type-string.
95 96 97 98 |
# File 'lib/klay/eip712.rb', line 95 def hash_type(primary_type, types) encoded_type = encode_type primary_type, types return Util.keccak256 encoded_type end |
.type_dependencies(primary_type, types, result = []) ⇒ Array
Scans all dependencies of a given type recursively and returns either all dependencies or none if not found.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/klay/eip712.rb', line 34 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 |
Instance Method Details
#encode_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes all data and types according to EIP-712.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/klay/eip712.rb', line 106 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 |
#encode_type(primary_type, types) ⇒ String
Encode types as an EIP-712 confrom string, e.g.,
MyType(string attribute)
.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/klay/eip712.rb', line 63 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 |
#enforce_typed_data(data) ⇒ Array
Enforces basic properties to be represented in the EIP-712 typed data structure: types, domain, message, etc.
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/klay/eip712.rb', line 151 def enforce_typed_data(data) data = JSON.parse data if Util.is_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 |
#hash(data) ⇒ String
Hashes a typed data structure with Keccak-256 to prepare a signed typed data operation respecting EIP-712.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/klay/eip712.rb', line 167 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 |
#hash_data(primary_type, data, types) ⇒ String
Recursively ABI-encodes and hashes all data and types.
140 141 142 143 |
# File 'lib/klay/eip712.rb', line 140 def hash_data(primary_type, data, types) encoded_data = encode_data primary_type, data, types return Util.keccak256 encoded_data end |
#hash_type(primary_type, types) ⇒ String
Hashes an EIP-712 confrom type-string.
95 96 97 98 |
# File 'lib/klay/eip712.rb', line 95 def hash_type(primary_type, types) encoded_type = encode_type primary_type, types return Util.keccak256 encoded_type end |
#type_dependencies(primary_type, types, result = []) ⇒ Array
Scans all dependencies of a given type recursively and returns either all dependencies or none if not found.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/klay/eip712.rb', line 34 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 |