# 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"rbsecp256k1"# Provides the `Eth` module.moduleEth# Defines handy tools for verifying and recovering signatures.moduleSignatureextendself# Provides a special signature error if signature is invalid.classSignatureError<StandardError;end# EIP-191 prefix byte 0x19EIP191_PREFIX_BYTE="\x19".freeze# EIP-712 version byte 0x01EIP712_VERSION_BYTE="\x01".freeze# Prefix message as per EIP-191 with 0x19 to ensure the data is not# valid RLP and thus not mistaken for a transaction.# EIP-191 Version byte: 0x45 (E)# ref: https://eips.ethereum.org/EIPS/eip-191## @param message [String] the message string to be prefixed.# @return [String] an EIP-191 prefixed stringdefprefix_message(message)"#{EIP191_PREFIX_BYTE}Ethereum Signed Message:\n#{message.size}#{message}"end# Dissects a signature blob of 65 bytes into its r, s, and v values.## @param signature [String] a Secp256k1 signature.# @return [String, String, String] the r, s, and v values.# @raise [SignatureError] if signature is of unknown size.defdissect(signature)signature=Util.bin_to_hexsignatureunlessUtil.is_hex?signaturesignature=Util.remove_hex_prefixsignatureifsignature.size<130raiseSignatureError,"Unknown signature length #{signature.size}!"endr=signature[0,64]s=signature[64,64]v=signature[128..]returnr,s,vend# Recovers a signature from arbitrary data without validation on a given chain.## @param blob [String] that arbitrary data to be recovered.# @param signature [String] the hex string containing the signature.# @param chain_id [Integer] the chain ID the signature should be recovered from.# @return [String] a hexa-decimal, uncompressed public key.# @raise [SignatureError] if signature is of invalid size or invalid v.defrecover(blob,signature,chain_id=Chain::ETHEREUM)context=Secp256k1::Context.newr,s,v=dissectsignaturev=v.to_i(16)raiseSignatureError,"Invalid signature v byte #{v} for chain ID #{chain_id}!"ifv<chain_idrecovery_id=Chain.to_recovery_idv,chain_idsignature_rs=Util.hex_to_bin"#{r}#{s}"recoverable_signature=context.recoverable_signature_from_compactsignature_rs,recovery_idpublic_key=recoverable_signature.recover_public_keyblobUtil.bin_to_hexpublic_key.uncompressedend# Recovers a public key from a prefixed, personal message and# a signature on a given chain. (EIP-191)## @param message [String] the message string.# @param signature [String] the hex string containing the signature.# @param chain_id [Integer] the chain ID the signature should be recovered from.# @return [String] a hexa-decimal, uncompressed public key.defpersonal_recover(message,signature,chain_id=Chain::ETHEREUM)prefixed_message=prefix_messagemessagehashed_message=Util.keccak256prefixed_messagerecoverhashed_message,signature,chain_idend# Recovers a public key from a typed data structure and a signature# on a given chain. (EIP-712)## @param typed_data [Array] all the data in the typed data structure to be recovered.# @param signature [String] the hex string containing the signature.# @param chain_id [Integer] the chain ID the signature should be recovered from.# @return [String] a hexa-decimal, uncompressed public key.defrecover_typed_data(typed_data,signature,chain_id=Chain::ETHEREUM)hash_to_sign=Eip712.hashtyped_datarecoverhash_to_sign,signature,chain_idend# Verifies a signature for a given public key or address.## @param blob [String] that arbitrary data to be verified.# @param signature [String] the hex string containing the signature.# @param public_key [String] either a public key or an Ethereum address.# @param chain_id [Integer] the chain ID used to sign.# @return [Boolean] true if signature matches provided public key.# @raise [SignatureError] if it cannot determine the type of data or public key.defverify(blob,signature,public_key,chain_id=Chain::ETHEREUM)recovered_key=nilifblob.instance_of?Arrayorblob.instance_of?Hash# recover Array from sign_typed_datarecovered_key=recover_typed_datablob,signature,chain_idelsifblob.instance_of?Stringandblob.encoding!=Encoding::ASCII_8BIT# recover message from personal_signrecovered_key=personal_recoverblob,signature,chain_idelsifblob.instance_of?Stringand(Util.is_hex?bloborblob.encoding==Encoding::ASCII_8BIT)# if nothing else, recover from arbitrary signaturerecovered_key=recoverblob,signature,chain_idend# raise if we cannot determine the data formatraiseSignatureError,"Unknown data format to verify: #{blob}"ifrecovered_key.nil?ifpublic_key.instance_of?Address# recovering using an Eth::Addressaddress=public_key.to_srecovered_address=Util.public_key_to_address(recovered_key).to_sreturnaddress==recovered_addresselsifpublic_key.instance_of?Secp256k1::PublicKey# recovering using an Secp256k1::PublicKeypublic_hex=Util.bin_to_hexpublic_key.uncompressedreturnpublic_hex==recovered_keyelsifpublic_key.size==42# recovering using an address Stringaddress=Address.new(public_key).to_srecovered_address=Util.public_key_to_address(recovered_key).to_sreturnaddress==recovered_addresselsifpublic_key.size==130# recovering using an uncompressed public key Stringreturnpublic_key==recovered_keyelse# raise if we cannot determine the public key format usedraiseSignatureError,"Invalid public key or address supplied #{public_key}!"endendendend