module Eth::Signature

def dissect(signature)

Raises:
  • (SignatureError) - if signature is of unknown size.

Returns:
  • (String, String, String) - the `r`, `s`, and `v` values.

Parameters:
  • signature (String) -- a concatenated Secp256k1 signature string.
def dissect(signature)
  signature = Util.bin_to_hex signature unless Util.hex? signature
  signature = Util.remove_hex_prefix signature
  if signature.size < 130
    raise SignatureError, "Unknown signature length #{signature.size}!"
  end
  r = signature[0, 64]
  s = signature[64, 64]
  v = signature[128..]
  return r, s, v
end

def personal_recover(message, signature, chain_id = Chain::ETHEREUM)

Returns:
  • (String) - a hexa-decimal, uncompressed public key.

Parameters:
  • chain_id (Integer) -- the chain ID the signature should be recovered from.
  • signature (String) -- the hex string containing the signature.
  • message (String) -- the message string.
def personal_recover(message, signature, chain_id = Chain::ETHEREUM)
  prefixed_message = prefix_message message
  hashed_message = Util.keccak256 prefixed_message
  recover hashed_message, signature, chain_id
end

def prefix_message(message)

Returns:
  • (String) - an EIP-191 prefixed string.

Parameters:
  • message (String) -- the message string to be prefixed.
def prefix_message(message)
  "#{EIP191_PREFIX_BYTE}Ethereum Signed Message:\n#{message.bytesize}#{message}"
end

def recover(blob, signature, chain_id = Chain::ETHEREUM)

Raises:
  • (SignatureError) - if signature is of invalid size or invalid v.

Returns:
  • (String) - a hexa-decimal, uncompressed public key.

Parameters:
  • chain_id (Integer) -- the chain ID the signature should be recovered from.
  • signature (String) -- the hex string containing the signature.
  • blob (String) -- that arbitrary data to be recovered.
def recover(blob, signature, chain_id = Chain::ETHEREUM)
  context = Secp256k1::Context.new
  r, s, v = dissect signature
  v = v.to_i(16)
  if !Chain.ledger? v and !Chain.legacy? v
    min_v = 2 * chain_id + 35
    raise SignatureError, "Invalid signature v byte #{v} for chain ID #{chain_id}!" if v < min_v
  end
  recovery_id = Chain.to_recovery_id v, chain_id
  signature_rs = Util.hex_to_bin "#{r}#{s}"
  recoverable_signature = context.recoverable_signature_from_compact signature_rs, recovery_id
  public_key = recoverable_signature.recover_public_key blob
  Util.bin_to_hex public_key.uncompressed
end

def recover_typed_data(typed_data, signature, chain_id = Chain::ETHEREUM)

Returns:
  • (String) - a hexa-decimal, uncompressed public key.

Parameters:
  • chain_id (Integer) -- the chain ID the signature should be recovered from.
  • signature (String) -- the hex string containing the signature.
  • typed_data (Array) -- all the data in the typed data structure to be recovered.
def recover_typed_data(typed_data, signature, chain_id = Chain::ETHEREUM)
  hash_to_sign = Eip712.hash typed_data
  recover hash_to_sign, signature, chain_id
end

def verify(blob, signature, public_key, chain_id = Chain::ETHEREUM)

Raises:
  • (SignatureError) - if it cannot determine the type of data or public key.

Returns:
  • (Boolean) - true if signature matches provided public key.

Parameters:
  • chain_id (Integer) -- the chain ID used to sign.
  • public_key (String) -- either a public key or an Ethereum address.
  • signature (String) -- the hex string containing the signature.
  • blob (String) -- that arbitrary data to be verified.
def verify(blob, signature, public_key, chain_id = Chain::ETHEREUM)
  recovered_key = nil
  if blob.instance_of? Array or blob.instance_of? Hash
    # recover Array from sign_typed_data
    recovered_key = recover_typed_data blob, signature, chain_id
  elsif blob.instance_of? String and blob.encoding != Encoding::ASCII_8BIT
    # recover message from personal_sign
    recovered_key = personal_recover blob, signature, chain_id
  elsif blob.instance_of? String and (Util.hex? blob or blob.encoding == Encoding::ASCII_8BIT)
    # if nothing else, recover from arbitrary signature
    recovered_key = recover blob, signature, chain_id
  end
  # raise if we cannot determine the data format
  raise SignatureError, "Unknown data format to verify: #{blob}" if recovered_key.nil?
  if public_key.instance_of? Address
    # recovering using an Eth::Address
    address = public_key.to_s
    recovered_address = Util.public_key_to_address(recovered_key).to_s
    return address == recovered_address
  elsif public_key.instance_of? Secp256k1::PublicKey
    # recovering using an Secp256k1::PublicKey
    public_hex = Util.bin_to_hex public_key.uncompressed
    return public_hex == recovered_key
  elsif public_key.size == 42
    # recovering using an address String
    address = Address.new(public_key).to_s
    recovered_address = Util.public_key_to_address(recovered_key).to_s
    return address == recovered_address
  elsif public_key.size == 130
    # recovering using an uncompressed public key String
    return public_key == recovered_key
  else
    # raise if we cannot determine the public key format used
    raise SignatureError, "Invalid public key or address supplied #{public_key}!"
  end
end