lib/eth/util.rb
# Copyright (c) 2016-2022 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. require "digest/keccak" require "rlp" # Provides the `Eth` module. module Eth # Defines handy tools for the `Eth` gem for convenience. module Util extend self # Generates an Ethereum address from a given compressed or # uncompressed binary or hexadecimal public key string. # # @param str [String] the public key to be converted. # @return [Eth::Address] an Ethereum address. def public_key_to_address(str) str = hex_to_bin str if is_hex? str bytes = keccak256(str[1..-1])[-20..-1] Address.new bin_to_prefixed_hex bytes end # Hashes a string with the Keccak-256 algorithm. # # @param str [String] a string to be hashed. # @return [String] a Keccak-256 hash of the given string. def keccak256(str) Digest::Keccak.new(256).digest str end # Unpacks a binary string to a hexa-decimal string. # # @param bin [String] a binary string to be unpacked. # @return [String] a hexa-decimal string. # @raise [TypeError] if value is not a string. def bin_to_hex(bin) raise TypeError, "Value must be an instance of String" unless bin.instance_of? String bin.unpack("H*").first end # Packs a hexa-decimal string into a binary string. Also works with # `0x`-prefixed strings. # # @param hex [String] a hexa-decimal string to be packed. # @return [String] a packed binary string. # @raise [TypeError] if value is not a string or string is not hex. def hex_to_bin(hex) raise TypeError, "Value must be an instance of String" unless hex.instance_of? String hex = remove_hex_prefix hex raise TypeError, "Non-hexadecimal digit found" unless is_hex? hex [hex].pack("H*") end # Prefixes a hexa-decimal string with `0x`. # # @param hex [String] a hex-string to be prefixed. # @return [String] a prefixed hex-string. def prefix_hex(hex) return hex if is_prefixed? hex return "0x#{hex}" end # Removes the `0x` prefix of a hexa-decimal string. # # @param hex [String] a prefixed hex-string. # @return [String] an unprefixed hex-string. def remove_hex_prefix(hex) return hex[2..-1] if is_prefixed? hex return hex end # Unpacks a binary string to a prefixed hexa-decimal string. # # @param bin [String] a binary string to be unpacked. # @return [String] a prefixed hexa-decimal string. def bin_to_prefixed_hex(bin) prefix_hex bin_to_hex bin end # Checks if a string is hex-adecimal. # # @param str [String] a string to be checked. # @return [String] a match if true; nil if not. def is_hex?(str) return false unless str.is_a? String str = remove_hex_prefix str str.match /\A[0-9a-fA-F]*\z/ end # Checks if a string is prefixed with `0x`. # # @param hex [String] a string to be checked. # @return [String] a match if true; nil if not. def is_prefixed?(hex) hex.match /\A0x/ end # Serializes an unsigned integer to big endian. # # @param num [Integer] unsigned integer to be serialized. # return [String] serialized big endian integer string. # raises [ArgumentError] if unsigned integer is out of bounds. def serialize_int_to_big_endian(num) num = num.to_i(16) if is_hex? num unless num.is_a? Integer and num >= 0 and num <= Abi::UINT_MAX raise ArgumentError, "Integer invalid or out of range: #{num}" end RLP::Sedes.big_endian_int.serialize num end # Deserializes big endian data string to integer. # # @param str [String] serialized big endian integer string. # @return [Integer] an deserialized unsigned integer. def deserialize_big_endian_to_int(str) RLP::Sedes.big_endian_int.deserialize str.sub(/\A(\x00)+/, "") end # Ceil and integer to the next multiple of 32 bytes. # # @param num [Integer] the number to ciel up. # @return [Integer] the ceiled to 32 integer. def ceil32(num) num % 32 == 0 ? num : (num + 32 - num % 32) end # Left-pad a number with a symbol. # # @param str [String] a serialized string to be padded. # @param sym [String] a symbol used for left-padding. # @param len [Integer] number of symbols for the final string. # @return [String] a left-padded serialized string of wanted size. def lpad(str, sym, len) return str if str.size >= len sym * (len - str.size) + str end # Left-pad a serialized string with zeros. # # @param str [String] a serialized string to be padded. # @param len [Integer] number of symbols for the final string. # @return [String] a zero-padded serialized string of wanted size. def zpad(str, len) lpad str, Abi::BYTE_ZERO, len end # Left-pad a hex number with zeros. # # @param hex [String] a hex-string to be padded. # @param len [Integer] number of symbols for the final string. # @return [String] a zero-padded serialized string of wanted size. def zpad_hex(hex, len = 32) zpad hex_to_bin(hex), len end # Left-pad an unsigned integer with zeros. # # @param num [Integer] an unsigned integer to be padded. # @param len [Integer] number of symbols for the final string. # @return [String] a zero-padded serialized string of wanted size. def zpad_int(num, len = 32) zpad serialize_int_to_big_endian(num), len end end end