From b56c622bc27a9e6768d45d91f22e15f187818ece Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Sun, 22 Jul 2018 19:40:51 +0900 Subject: [PATCH 1/2] Add web3/metamask-compatible personal_sign and personal_recover --- README.md | 10 ++++++++++ lib/eth/key.rb | 7 +++++++ lib/eth/utils.rb | 4 ++++ spec/eth/key_spec.rb | 23 +++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/README.md b/README.md index 11cd619..fde2f37 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,16 @@ Or add a checksum to an existing address: Eth::Utils.format_address "0x4bc787699093f11316e819b5692be04a712c4e69" # => "0x4bc787699093f11316e819B5692be04A712C4E69" ``` +### Personal Signatures + +You can generate and verify web3/metamask-compatible signatures: + +```ruby +message = 'test' +signature = '0x3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c' +Eth::Key.personal_recover(message, Eth::Utils.hex_to_bin(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. diff --git a/lib/eth/key.rb b/lib/eth/key.rb index fa6949a..1bdfb64 100644 --- a/lib/eth/key.rb +++ b/lib/eth/key.rb @@ -16,6 +16,9 @@ def self.decrypt(data, password) new priv: priv end + def self.personal_recover(message, signature) + OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), signature.bytes.rotate(-1).pack('c*')) + end def initialize(priv: nil) @private_key = MoneyTree::PrivateKey.new key: priv @@ -55,6 +58,10 @@ def verify_signature(message, signature) public_hex == OpenSsl.recover_compact(hash, signature) end + def personal_sign(message) + sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*') + end + private diff --git a/lib/eth/utils.rb b/lib/eth/utils.rb index 0ad6588..ece7369 100644 --- a/lib/eth/utils.rb +++ b/lib/eth/utils.rb @@ -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] diff --git a/spec/eth/key_spec.rb b/spec/eth/key_spec.rb index 64c165b..f5bfa0d 100644 --- a/spec/eth/key_spec.rb +++ b/spec/eth/key_spec.rb @@ -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) { hex_to_bin "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" } From 97d70a0daa69c4085f10d1a254e6f956b3e7ba4e Mon Sep 17 00:00:00 2001 From: Stefan Buhrmester Date: Tue, 31 Jul 2018 01:54:14 +0900 Subject: [PATCH 2/2] expect and return hex sig instead binary sig --- README.md | 8 ++++++-- lib/eth/key.rb | 5 +++-- spec/eth/key_spec.rb | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fde2f37..393abdc 100644 --- a/README.md +++ b/README.md @@ -94,12 +94,16 @@ Eth::Utils.format_address "0x4bc787699093f11316e819b5692be04a712c4e69" # => "0x4 ### Personal Signatures -You can generate and verify web3/metamask-compatible 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, Eth::Utils.hex_to_bin(signature)) # => 043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc +Eth::Key.personal_recover(message, signature) # => 043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc ``` ### Configure diff --git a/lib/eth/key.rb b/lib/eth/key.rb index 1bdfb64..d9e8da7 100644 --- a/lib/eth/key.rb +++ b/lib/eth/key.rb @@ -17,7 +17,8 @@ def self.decrypt(data, password) end def self.personal_recover(message, signature) - OpenSsl.recover_compact(Utils.keccak256(Utils.prefix_message(message)), signature.bytes.rotate(-1).pack('c*')) + 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) @@ -59,7 +60,7 @@ def verify_signature(message, signature) end def personal_sign(message) - sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*') + Utils.bin_to_hex(sign(Utils.prefix_message(message)).bytes.rotate(1).pack('c*')) end diff --git a/spec/eth/key_spec.rb b/spec/eth/key_spec.rb index f5bfa0d..86b9df6 100644 --- a/spec/eth/key_spec.rb +++ b/spec/eth/key_spec.rb @@ -61,7 +61,7 @@ describe ".personal_recover" do let(:message) { "test" } - let(:signature) { hex_to_bin "3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c" } + let(:signature) { "3eb24bd327df8c2b614c3f652ec86efe13aa721daf203820241c44861a26d37f2bffc6e03e68fc4c3d8d967054c9cb230ed34339b12ef89d512b42ae5bf8c2ae1c" } let(:public_hex) { "043e5b33f0080491e21f9f5f7566de59a08faabf53edbc3c32aaacc438552b25fdde531f8d1053ced090e9879cbf2b0d1c054e4b25941dab9254d2070f39418afc" } it "it can recover a public key from a signature generated with web3/metamask" do