Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
se3000 committed Mar 28, 2017
2 parents bab319c + a07233d commit 4c514ec
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 13 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

## [0.4.2]

### Added
- Address#valid? to validate EIP55 checksums.
- Address#checksummed to generate EIP55 checksums.
- Utils.valid_address? to easily validate EIP55 checksums.
- Utils.format_address to easily convert an address to EIP55 checksummed.

### Changed
- Dependencies no longer include Ethereum::Base. Eth now implements those helpers directly and includes ffi, digest-sha3, and rlp directly.


## [0.4.1]

### Changed
- Tx#hash includes the '0x' hex prefix.

Expand Down
30 changes: 22 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ Or install it yourself as:
## Usage

### Keys
Create a new key:
Create a new public/private key and get its address:
```ruby
key = Eth::Key.new
key.private_hex
key.public_hex
key.address # EIP55 checksummed address
```
Or import and existing one:
```ruby
Expand All @@ -48,6 +51,24 @@ Or decode an encoded raw transaction:
tx = Eth::Tx.decode hex
```

Then sign the transaction:
```ruby
tx.sign key
```
Get the raw transaction with `tx.hex`, and broadcast it through any Ethereum node. Or, just get the TXID with `tx.hash`.

### Utils

Validate an [EIP55](https://github.com/ethereum/EIPs/issues/55) checksummed address:
```ruby
Eth::Utils.valid_address? address
```

Or add a checksum to an existing address:
```ruby
Eth::Utils.valid_address? "0x4bc787699093f11316e819b5692be04a712c4e69" # => "0x4bc787699093f11316e819B5692be04A712C4E69"
```

### Configure
In order to prevent replay attacks, you must specify which Ethereum chain your transactions are created for. See [EIP 155](https://github.com/ethereum/EIPs/issues/155) for more detail.
```ruby
Expand All @@ -56,13 +77,6 @@ Eth.configure do |config|
end
```

Then sign the transaction:
```ruby
tx.sign key
```
Get the raw transaction with `tx.hex`, and broadcast it through any Ethereum node. Or, just get the TXID with `tx.hash`.


## Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/se3000/ethereum-tx. Tests are encouraged.
Expand Down
1 change: 1 addition & 0 deletions lib/eth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Eth
BYTE_ZERO = "\x00".freeze
UINT_MAX = 2**256 - 1

autoload :Address, 'eth/address'
autoload :Gas, 'eth/gas'
autoload :Key, 'eth/key'
autoload :OpenSsl, 'eth/open_ssl'
Expand Down
62 changes: 62 additions & 0 deletions lib/eth/address.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module Eth
class Address

def initialize(address)
@address = Utils.prefix_hex(address)
end

def valid?
if !matches_any_format?
false
elsif not_checksummed?
true
else
checksum_matches?
end
end

def checksummed
raise "Invalid address: #{address}" unless matches_any_format?

cased = unprefixed.chars.zip(checksum.chars).map do |char, check|
check.match(/[0-7]/) ? char.downcase : char.upcase
end

Utils.prefix_hex(cased.join)
end


private

attr_reader :address

def checksum_matches?
address == checksummed
end

def not_checksummed?
all_uppercase? || all_lowercase?
end

def all_uppercase?
address.match(/(?:0[xX])[A-F0-9]{40}/)
end

def all_lowercase?
address.match(/(?:0[xX])[a-f0-9]{40}/)
end

def matches_any_format?
address.match(/\A(?:0[xX])[a-fA-F0-9]{40}\z/)
end

def checksum
Utils.bin_to_hex(Utils.keccak256 unprefixed.downcase)
end

def unprefixed
Utils.remove_hex_prefix address
end

end
end
11 changes: 10 additions & 1 deletion lib/eth/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def bin_to_prefixed_hex(binary)
def public_key_to_address(hex)
bytes = hex_to_bin(hex)
address_bytes = Utils.keccak256(bytes[1..-1])[-20..-1]
bin_to_prefixed_hex address_bytes
format_address bin_to_prefixed_hex(address_bytes)
end

def sha256(x)
Expand Down Expand Up @@ -97,6 +97,15 @@ def zpad_hex(s, l=32)
zpad decode_hex(s), l
end

def valid_address?(address)
Address.new(address).valid?
end

def format_address(address)
Address.new(address).checksummed
end



private

Expand Down
2 changes: 1 addition & 1 deletion lib/eth/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Eth
VERSION = "0.4.1"
VERSION = "0.4.2"
end
133 changes: 133 additions & 0 deletions spec/eth/address_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
describe Eth::Address do
describe "#valid?" do
context "given an address with a valid checksum" do
let(:addresses) do
[
"0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
]
end

it "returns true" do
addresses.each do |address|
expect(Eth::Address.new address).to be_valid
end
end
end

context "given an address with an invalid checksum" do
let(:addresses) do
[
"0x5AAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
"0xFB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
"0xDbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
"0xd1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
]
end

it "returns false" do
addresses.each do |address|
expect(Eth::Address.new address).not_to be_valid
end
end
end

context "given an address with all uppercase letters" do
let(:addresses) do
[
"0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED",
"0xFB6916095CA1DF60BB79CE92CE3EA74C37C5D359",
"0xDBF03B407C01E7CD3CBEA99509D93F8DDDC8C6FB",
"0xD1220A0CF47C7B9BE7A2E6BA89F429762E7B9ADB",
# common EIP55 examples
"0x52908400098527886E0F7030069857D2E4169EE7",
"0x8617E340B3D01FA5F11F306F4090FD50E238070D",
]
end

it "returns true" do
addresses.each do |address|
expect(Eth::Address.new address).to be_valid
end
end
end

context "given an address with all lowercase letters" do
let(:addresses) do
[
"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed",
"0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359",
"0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb",
"0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
# common EIP55 examples
'0xde709f2102306220921060314715629080e2fb77',
'0x27b1fdb04752bbc536007a920d24acb045561c26',
]
end

it "returns true" do
addresses.each do |address|
expect(Eth::Address.new address).to be_valid
end
end
end

context "given an invalid address" do
let(:addresses) do
[
"0x5aaeb6053f3e94c9b9a09f33669435e7ef1beae",
"0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359d",
"0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAE",
"0xFB6916095CA1DF60BB79CE92CE3EA74C37C5D359D",
]
end

it "returns true" do
addresses.each do |address|
expect(Eth::Address.new address).not_to be_valid
end
end
end
end

describe "#checksummed" do
let(:addresses) do
[
# downcased
["0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"],
["0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"],
["0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"],
["0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"],
# upcased
["0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"],
["0xFB6916095CA1DF60BB79CE92CE3EA74C37C5D359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"],
["0xDBF03B407C01E7CD3CBEA99509D93F8DDDC8C6FB", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"],
["0xD1220A0CF47C7B9BE7A2E6BA89F429762E7B9ADB", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"],
# checksummed
["0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed"],
["0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359"],
["0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"],
["0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"],
]
end

it "follows EIP55 standard" do
addresses.each do |plain, checksummed|
address = Eth::Address.new(plain)
expect(address.checksummed).to eq checksummed
end
end

context "given an invalid address" do
let(:bad) { "0x#{SecureRandom.hex(21)[0..40]}" }

it "raises an error" do
expect {
Eth::Address.new(bad).checksummed
}.to raise_error "Invalid address: #{bad}"
end
end
end
end
2 changes: 1 addition & 1 deletion spec/eth/key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
describe "#address" do
subject { key.address }
let(:priv) { 'c3a4349f6e57cfd2cbba275e3b3d15a2e4cf00c89e067f6e05bfee25208f9cbb' }
it { is_expected.to eq('0x759b427456623a33030bbc2195439c22a8a51d25') }
it { is_expected.to eq('0x759b427456623a33030bbC2195439C22A8a51d25') }
it { is_expected.to eq(key.to_address) }
end
end
11 changes: 10 additions & 1 deletion spec/eth/utils_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
end

describe ".public_key_to_addres" do
let(:address) { "0x8abc566c5198bc6993526db697ffe58ce4e2425a" }
let(:address) { "0x8ABC566c5198bc6993526DB697FFe58ce4e2425A" }
let(:pub) { "0463a1ad6824c03f81ad6c9c224384172c67f6bfd2dbde8c4747a033629b531ae3284db3045e4e40c2b865e22a806ae7dff9264299ea8696321f689d6e134d937e" }

it "turns a hex public key into a hex address" do
Expand Down Expand Up @@ -81,4 +81,13 @@
end
end

describe ".format_address" do
let(:address) { "0x5AAEB6053F3E94C9B9A09F33669435E7EF1BEAED" }
subject { Eth::Utils.format_address address }

it "returns checksummed addresses" do
expect(subject).to eq("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed")
end
end

end
2 changes: 1 addition & 1 deletion spec/ethereum_tests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

tx = Eth::Tx.decode json['rlp']

expect(tx.from).to eq "0x#{json['sender']}"
expect(tx.from.downcase).to eq "0x#{json['sender']}"
expect(tx.v).to eq json_tx['v'].to_i(16)
expect(tx.r).to eq json_tx['r'].to_i(16)
expect(tx.s).to eq json_tx['s'].to_i(16)
Expand Down

0 comments on commit 4c514ec

Please sign in to comment.