Skip to content

Commit

Permalink
Merge pull request #30 from buhrmi/master
Browse files Browse the repository at this point in the history
Add web3/metamask-compatible personal_sign and personal_recover, fixes #18
  • Loading branch information
se3000 authored Aug 5, 2018
2 parents cd59755 + 97d70a0 commit 82e89b3
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 0 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ Or add a checksum to an existing address:
Eth::Utils.format_address "0x4bc787699093f11316e819b5692be04a712c4e69" # => "0x4bc787699093f11316e819B5692be04A712C4E69"
```

### Personal Signatures

You can recover public keys and generate web3/metamask-compatible signatures:

```ruby
# Generate signature
key.personal_sign('hello world')

# Recover signature
message = 'test'
signature = '0x3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c'
Eth::Key.personal_recover(message, signature) # => 043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc
```

### 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.
Expand Down
8 changes: 8 additions & 0 deletions lib/eth/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ def self.decrypt(data, password)
new priv: priv
end

def self.personal_recover(message, signature)
bin_signature = Utils.hex_to_bin(signature).bytes.rotate(-1).pack('c*')
OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), bin_signature)
end

def initialize(priv: nil)
@private_key = MoneyTree::PrivateKey.new key: priv
Expand Down Expand Up @@ -55,6 +59,10 @@ def verify_signature(message, signature)
public_hex == OpenSsl.recover_compact(hash, signature)
end

def personal_sign(message)
Utils.bin_to_hex(sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*'))
end


private

Expand Down
4 changes: 4 additions & 0 deletions lib/eth/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def bin_to_prefixed_hex(binary)
prefix_hex bin_to_hex(binary)
end

def prefix_message(message)
"\x19Ethereum Signed Message:\n#{message.length}#{message}"
end

def public_key_to_address(hex)
bytes = hex_to_bin(hex)
address_bytes = Utils.keccak256(bytes[1..-1])[-20..-1]
Expand Down
23 changes: 23 additions & 0 deletions spec/eth/key_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,29 @@
end
end

describe "#personal_sign" do
let(:message) { "Hi Mom!" }

it "signs a message so that the public key can be recovered with personal_recover" do
10.times do
signature = key.personal_sign message
expect(Eth::Key.personal_recover message, signature).to eq(key.public_hex)
end
end
end

describe ".personal_recover" do
let(:message) { "test" }
let(:signature) { "3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c" }
let(:public_hex) { "043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc" }

it "it can recover a public key from a signature generated with web3/metamask" do
10.times do
expect(Eth::Key.personal_recover message, signature).to eq(public_hex)
end
end
end

describe "#verify_signature" do
let(:priv) { '5a37533acfa3ff9386aed01e16c0e7a79038ce05cc383e290d360b8ce9cd6fdf' }
let(:signature) { hex_to_bin "1ce2f13b4123a23a4a280ac4adcba1ffa3f3848f494dc1de440af43f677e0e01260fb4667ed117d555659b249702c8215162b3f0ee09628813a4ef83616f99f180" }
Expand Down

0 comments on commit 82e89b3

Please sign in to comment.