Skip to content

Commit

Permalink
eth/chains: implement eip 155 for replay protection (#20)
Browse files Browse the repository at this point in the history
* eth/chains: implement eip 155 for replay protection

* spec: test for legacy (pre 155) v values

* eth/chains: fix yard docs
  • Loading branch information
q9f committed Dec 14, 2021
1 parent 6c3a94f commit 80c27fe
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 1 deletion.
3 changes: 2 additions & 1 deletion lib/eth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ module Eth
end

# Loads the `Eth` module classes.
require 'eth/key'
require 'eth/address'
require 'eth/chains'
require 'eth/key'
require 'eth/utils'
require 'eth/version'
120 changes: 120 additions & 0 deletions lib/eth/chains.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# 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.

# Provides the `Eth` module.
module Eth

# Encapsulates `Chains` IDs and utilities for EIP-155 compatibility.
# ref: https://eips.ethereum.org/EIPS/eip-155
module Chains
extend self

# Chain ID for Ethereum mainnet
ETHEREUM = 1

# Chain ID for Expanse mainnet
EXPANSE = 2

# Chain ID for Optimistic Ethereum mainnet
OPTIMISM = 10

# Chain ID for Ethereum Classic mainnet
CLASSIC = 61

# Chain ID for POA Network mainnet
POA_NET = 99

# Chain ID for xDAI mainnet
XDAI = 100

# Chain ID for Arbitrum mainnet
ARBITRUM = 42161

# Chain ID for Morden (Ethereum) testnet
MORDEN = 2

# Chain ID for Ropsten testnet
ROPSTEN = 3

# Chain ID for Rinkeby testnet
RINKEBY = 4

# Chain ID for Goerli testnet
GOERLI = 5

# Chain ID for Kotti testnet
KOTTI = 6

# Chain ID for Kovan testnet
KOVAN = 42

# Chain ID for Morden (Classic) testnet
MORDEN_CLASSIC = 62

# Chain ID for Mordor testnet
MORDOR = 63

# Chain ID for Optimistik Kovan testnet
KOVAN_OPTIMISM = 69

# Chain ID for Arbitrum xDAI testnet
XDAI_ARBITRUM = 200

# Chain ID for Optimistic Goerli testnet
GOERLI_OPTIMISM = 420

# Chain ID for Arbitrum Rinkeby testnet
RINKEBY_ARBITRUM = 421611

# Chain ID for the geth private network preset
PRIVATE_GETH = 1337

# Indicates wether the given `v` indicates a legacy chain value
# without EIP-155 replay protection.
#
# @param v [Integer] the signature's `v` value
# @return [Boolean] true if legacy value
def is_legacy? v
[27, 28].include? v
end

# Convert a given `v` value to an ECDSA recovery id for the given
# EIP-155 chain ID.
#
# @param v [Integer] the signature's `v` value
# @param chain [Integer] the chain id the signature was generated on.
# @return [Integer] the recovery id corresponding to `v`.
# @raise [ArgumentError] if the given `v` is invalid.
def to_recov v, chain = ETHEREUM
x = 0 + 2 * chain + 35
y = 1 + 2 * chain + 35
if is_legacy? v
return v - 27
elsif [x, y].include? v
return v - 35 - 2 * chain
else
raise ArgumentError, "Invalid v value for chain #{chain}. Invalid chain ID?"
end
end

# Converts a recovery ID into the expected `v` on a given chain.
#
# @param recov [Integer] signature recovery id.
# @param chain [Integer] the chain id the signature was generated on.
# @return [Integer] the signature's `v` value.
def to_v recov, chain = ETHEREUM
v = 2 * chain + 35 + recov
end
end
end
82 changes: 82 additions & 0 deletions spec/eth/chains_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'spec_helper'

describe Eth::Chains do
describe "#CHAIN_ID" do
it "has EIP155 chain ids for mainnets, testnets, and devnets" do
# Chain IDs for selected mainnets
expect(Eth::Chains::ETHEREUM).to eq 1
expect(Eth::Chains::EXPANSE).to eq 2
expect(Eth::Chains::OPTIMISM).to eq 10
expect(Eth::Chains::CLASSIC).to eq 61
expect(Eth::Chains::POA_NET).to eq 99
expect(Eth::Chains::XDAI).to eq 100
expect(Eth::Chains::ARBITRUM).to eq 42161

# Chain IDs for selected testnets
expect(Eth::Chains::MORDEN).to eq 2
expect(Eth::Chains::ROPSTEN).to eq 3
expect(Eth::Chains::RINKEBY).to eq 4
expect(Eth::Chains::GOERLI).to eq 5
expect(Eth::Chains::KOTTI).to eq 6
expect(Eth::Chains::KOVAN).to eq 42
expect(Eth::Chains::MORDEN_CLASSIC).to eq 62
expect(Eth::Chains::MORDOR).to eq 63
expect(Eth::Chains::KOVAN_OPTIMISM).to eq 69
expect(Eth::Chains::XDAI_ARBITRUM).to eq 200
expect(Eth::Chains::GOERLI_OPTIMISM).to eq 420
expect(Eth::Chains::RINKEBY_ARBITRUM).to eq 421611

# Chain IDs for selected private networks
expect(Eth::Chains::PRIVATE_GETH).to eq 1337
end
end

describe ".is_legacy" do
it "can detect legacy values for v" do
expect(Eth::Chains.is_legacy? 0).not_to be_truthy
expect(Eth::Chains.is_legacy? 1).not_to be_truthy
expect(Eth::Chains.is_legacy? 27).to be_truthy
expect(Eth::Chains.is_legacy? 28).to be_truthy
expect(Eth::Chains.is_legacy? 37).not_to be_truthy
expect(Eth::Chains.is_legacy? 38).not_to be_truthy
end
end

describe ".to_v .to_recovery_id" do
it "can convert ethereum recovery ids to v" do
expect(Eth::Chains.to_v 0).to be 37
expect(Eth::Chains.to_v 1).to be 38
end
it "can convert other chain's recovery ids to v" do
expect(Eth::Chains.to_v 0, Eth::Chains::CLASSIC).to be 157
expect(Eth::Chains.to_v 1, Eth::Chains::XDAI).to be 236
expect(Eth::Chains.to_v 0, Eth::Chains::ARBITRUM).to be 84357
expect(Eth::Chains.to_v 1, Eth::Chains::MORDEN_CLASSIC).to be 160
expect(Eth::Chains.to_v 0, Eth::Chains::GOERLI_OPTIMISM).to be 875
expect(Eth::Chains.to_v 1, Eth::Chains::RINKEBY_ARBITRUM).to be 843258
expect(Eth::Chains.to_v 0, Eth::Chains::PRIVATE_GETH).to be 2709
end
it "can recover v from ethereum recovery id" do
expect(Eth::Chains.to_recov 37).to be 0
expect(Eth::Chains.to_recov 38).to be 1

# legacy v
expect(Eth::Chains.to_recov 27).to be 0
expect(Eth::Chains.to_recov 28).to be 1
end
it "can recover v from other chain's recovery id" do
expect(Eth::Chains.to_recov 157, Eth::Chains::CLASSIC).to be 0
expect(Eth::Chains.to_recov 236, Eth::Chains::XDAI).to be 1
expect(Eth::Chains.to_recov 84357, Eth::Chains::ARBITRUM).to be 0
expect(Eth::Chains.to_recov 160, Eth::Chains::MORDEN_CLASSIC).to be 1
expect(Eth::Chains.to_recov 875, Eth::Chains::GOERLI_OPTIMISM).to be 0
expect(Eth::Chains.to_recov 843258, Eth::Chains::RINKEBY_ARBITRUM).to be 1
expect(Eth::Chains.to_recov 2709, Eth::Chains::PRIVATE_GETH).to be 0
end
it "raises an error for invalid v on chain ids" do
expect {Eth::Chains.to_recov 0}.to raise_error ArgumentError
expect {Eth::Chains.to_recov 36}.to raise_error ArgumentError
expect {Eth::Chains.to_recov 843258, Eth::Chains::PRIVATE_GETH}.to raise_error ArgumentError
end
end
end

0 comments on commit 80c27fe

Please sign in to comment.