moac_eth 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,197 @@
1
+ # originally lifted from https://github.com/lian/bitcoin-ruby
2
+ # thanks to everyone there for figuring this out
3
+
4
+ module MoacEth
5
+ class OpenSsl
6
+ extend FFI::Library
7
+
8
+ if FFI::Platform.windows?
9
+ ffi_lib 'libeay32', 'ssleay32'
10
+ else
11
+ ffi_lib ['libssl.so.1.0.0', 'libssl.so.10', 'ssl']
12
+ end
13
+
14
+ NID_secp256k1 = 714
15
+ POINT_CONVERSION_COMPRESSED = 2
16
+ POINT_CONVERSION_UNCOMPRESSED = 4
17
+
18
+ attach_function :SSL_library_init, [], :int
19
+ attach_function :ERR_load_crypto_strings, [], :void
20
+ attach_function :SSL_load_error_strings, [], :void
21
+ attach_function :RAND_poll, [], :int
22
+
23
+ attach_function :BN_CTX_free, [:pointer], :int
24
+ attach_function :BN_CTX_new, [], :pointer
25
+ attach_function :BN_add, [:pointer, :pointer, :pointer], :int
26
+ attach_function :BN_bin2bn, [:pointer, :int, :pointer], :pointer
27
+ attach_function :BN_bn2bin, [:pointer, :pointer], :int
28
+ attach_function :BN_cmp, [:pointer, :pointer], :int
29
+ attach_function :BN_dup, [:pointer], :pointer
30
+ attach_function :BN_free, [:pointer], :int
31
+ attach_function :BN_mod_inverse, [:pointer, :pointer, :pointer, :pointer], :pointer
32
+ attach_function :BN_mod_mul, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
33
+ attach_function :BN_mod_sub, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
34
+ attach_function :BN_mul_word, [:pointer, :int], :int
35
+ attach_function :BN_new, [], :pointer
36
+ attach_function :BN_num_bits, [:pointer], :int
37
+ attach_function :BN_rshift, [:pointer, :pointer, :int], :int
38
+ attach_function :BN_set_word, [:pointer, :int], :int
39
+ attach_function :ECDSA_SIG_free, [:pointer], :void
40
+ attach_function :ECDSA_do_sign, [:pointer, :uint, :pointer], :pointer
41
+ attach_function :EC_GROUP_get_curve_GFp, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
42
+ attach_function :EC_GROUP_get_degree, [:pointer], :int
43
+ attach_function :EC_GROUP_get_order, [:pointer, :pointer, :pointer], :int
44
+ attach_function :EC_KEY_free, [:pointer], :int
45
+ attach_function :EC_KEY_get0_group, [:pointer], :pointer
46
+ attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
47
+ attach_function :EC_KEY_set_conv_form, [:pointer, :int], :void
48
+ attach_function :EC_KEY_set_private_key, [:pointer, :pointer], :int
49
+ attach_function :EC_KEY_set_public_key, [:pointer, :pointer], :int
50
+ attach_function :EC_POINT_free, [:pointer], :int
51
+ attach_function :EC_POINT_mul, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int
52
+ attach_function :EC_POINT_new, [:pointer], :pointer
53
+ attach_function :EC_POINT_set_compressed_coordinates_GFp, [:pointer, :pointer, :pointer, :int, :pointer], :int
54
+ attach_function :i2o_ECPublicKey, [:pointer, :pointer], :uint
55
+
56
+ class << self
57
+ def BN_num_bytes(ptr)
58
+ (BN_num_bits(ptr) + 7) / 8
59
+ end
60
+
61
+ def sign_compact(hash, private_key, public_key_hex)
62
+ private_key = [private_key].pack("H*") if private_key.bytesize >= 64
63
+ pubkey_compressed = false
64
+
65
+ init_ffi_ssl
66
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
67
+ priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new())
68
+
69
+ group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new()
70
+ EC_GROUP_get_order(group, order, ctx)
71
+
72
+ pub_key = EC_POINT_new(group)
73
+ EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
74
+ EC_KEY_set_private_key(eckey, priv_key)
75
+ EC_KEY_set_public_key(eckey, pub_key)
76
+
77
+ signature = ECDSA_do_sign(hash, hash.bytesize, eckey)
78
+
79
+ BN_free(order)
80
+ BN_CTX_free(ctx)
81
+ EC_POINT_free(pub_key)
82
+ BN_free(priv_key)
83
+ EC_KEY_free(eckey)
84
+
85
+ buf, rec_id, head = FFI::MemoryPointer.new(:uint8, 32), nil, nil
86
+ r, s = signature.get_array_of_pointer(0, 2).map{|i| BN_bn2bin(i, buf); buf.read_string(BN_num_bytes(i)).rjust(32, "\x00") }
87
+
88
+ if signature.get_array_of_pointer(0, 2).all?{|i| BN_num_bits(i) <= 256 }
89
+ 4.times{|i|
90
+ head = [ MoacEth.v_base + i ].pack("C")
91
+ if public_key_hex == recover_public_key_from_signature(hash, [head, r, s].join, i, pubkey_compressed)
92
+ rec_id = i; break
93
+ end
94
+ }
95
+ end
96
+
97
+ ECDSA_SIG_free(signature)
98
+
99
+ [ head, [r,s] ].join if rec_id
100
+ end
101
+
102
+ def recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed)
103
+ return nil if rec_id < 0 or signature.bytesize != 65
104
+ init_ffi_ssl
105
+
106
+ signature = FFI::MemoryPointer.from_string(signature)
107
+ r = BN_bin2bn(signature[1], 32, BN_new())
108
+ s = BN_bin2bn(signature[33], 32, BN_new())
109
+
110
+ _n, i = 0, rec_id / 2
111
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
112
+
113
+ EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed
114
+
115
+ group = EC_KEY_get0_group(eckey)
116
+ order = BN_new()
117
+ EC_GROUP_get_order(group, order, nil)
118
+ x = BN_dup(order)
119
+ BN_mul_word(x, i)
120
+ BN_add(x, x, r)
121
+
122
+ field = BN_new()
123
+ EC_GROUP_get_curve_GFp(group, field, nil, nil, nil)
124
+
125
+ if BN_cmp(x, field) >= 0
126
+ bn_free_each r, s, order, x, field
127
+ EC_KEY_free(eckey)
128
+ return nil
129
+ end
130
+
131
+ big_r = EC_POINT_new(group)
132
+ EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil)
133
+
134
+ big_q = EC_POINT_new(group)
135
+ n = EC_GROUP_get_degree(group)
136
+ e = BN_bin2bn(message_hash, message_hash.bytesize, BN_new())
137
+ BN_rshift(e, e, 8 - (n & 7)) if 8 * message_hash.bytesize > n
138
+
139
+ ctx = BN_CTX_new()
140
+ zero, rr, sor, eor = BN_new(), BN_new(), BN_new(), BN_new()
141
+ BN_set_word(zero, 0)
142
+ BN_mod_sub(e, zero, e, order, ctx)
143
+ BN_mod_inverse(rr, r, order, ctx)
144
+ BN_mod_mul(sor, s, rr, order, ctx)
145
+ BN_mod_mul(eor, e, rr, order, ctx)
146
+ EC_POINT_mul(group, big_q, eor, big_r, sor, ctx)
147
+ EC_KEY_set_public_key(eckey, big_q)
148
+ BN_CTX_free(ctx)
149
+
150
+ bn_free_each r, s, order, x, field, e, zero, rr, sor, eor
151
+ [big_r, big_q].each{|j| EC_POINT_free(j) }
152
+
153
+ recover_public_hex eckey
154
+ end
155
+
156
+ def recover_compact(hash, signature)
157
+ return false if signature.bytesize != 65
158
+
159
+ version = signature.unpack('C')[0]
160
+ v_base = MoacEth.replayable_v?(version) ? MoacEth.replayable_chain_id : MoacEth.v_base
161
+ return false if version < v_base
162
+
163
+ recover_public_key_from_signature(hash, signature, (version - v_base), false)
164
+ end
165
+
166
+ def init_ffi_ssl
167
+ return if @ssl_loaded
168
+ SSL_library_init()
169
+ ERR_load_crypto_strings()
170
+ SSL_load_error_strings()
171
+ RAND_poll()
172
+ @ssl_loaded = true
173
+ end
174
+
175
+
176
+ private
177
+
178
+ def bn_free_each(*list)
179
+ list.each{|j| BN_free(j) }
180
+ end
181
+
182
+ def recover_public_hex(eckey)
183
+ length = i2o_ECPublicKey(eckey, nil)
184
+ buf = FFI::MemoryPointer.new(:uint8, length)
185
+ ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
186
+ pub_hex = if i2o_ECPublicKey(eckey, ptr) == length
187
+ buf.read_string(length).unpack("H*")[0]
188
+ end
189
+
190
+ EC_KEY_free(eckey)
191
+
192
+ pub_hex
193
+ end
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,7 @@
1
+ module MoacEth
2
+ class Secp256k1
3
+
4
+ N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
5
+
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module MoacEth
2
+ module Sedes
3
+ include RLP::Sedes
4
+
5
+ extend self
6
+
7
+ def address
8
+ Binary.fixed_length(20, allow_empty: true)
9
+ end
10
+
11
+ def int20
12
+ BigEndianInt.new(20)
13
+ end
14
+
15
+ def int32
16
+ BigEndianInt.new(32)
17
+ end
18
+
19
+ def int256
20
+ BigEndianInt.new(256)
21
+ end
22
+
23
+ def hash32
24
+ Binary.fixed_length(32)
25
+ end
26
+
27
+ def trie_root
28
+ Binary.fixed_length(32, allow_empty: true)
29
+ end
30
+
31
+ def big_endian_int
32
+ RLP::Sedes.big_endian_int
33
+ end
34
+
35
+ def binary
36
+ RLP::Sedes.binary
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,158 @@
1
+ module MoacEth
2
+ class Tx
3
+
4
+ include RLP::Sedes::Serializable
5
+ extend Sedes
6
+
7
+ set_serializable_fields({
8
+ nonce: big_endian_int,
9
+ systemContract: big_endian_int,
10
+ gas_price: big_endian_int,
11
+ gas_limit: big_endian_int,
12
+ to: address,
13
+ value: big_endian_int,
14
+ data_bin: binary,
15
+ shardingFlag: big_endian_int,
16
+ via: binary,
17
+ v: big_endian_int,
18
+ r: big_endian_int,
19
+ s: big_endian_int
20
+ })
21
+
22
+ attr_writer :signature
23
+
24
+ def self.decode(data)
25
+ data = Utils.hex_to_bin(data) if data.match(/\A(?:0x)?\h+\Z/)
26
+ deserialize(RLP.decode data)
27
+ end
28
+
29
+ def initialize(params)
30
+
31
+ fields = params.merge({v: MoacEth.chain_id, r: 0, s: 0})
32
+ fields[:to] = Utils.normalize_address(fields[:to])
33
+
34
+ if params[:data]
35
+ self.data = params.delete(:data)
36
+ fields[:data_bin] = data_bin
37
+ end
38
+ serializable_initialize fields
39
+
40
+ check_transaction_validity
41
+ end
42
+
43
+ def unsigned_encoded
44
+ RLP.encode(unsigned, sedes: sedes)
45
+ end
46
+
47
+ def signing_data
48
+ Utils.bin_to_prefixed_hex unsigned_encoded
49
+ end
50
+
51
+ def encoded
52
+ RLP.encode self
53
+ end
54
+
55
+ def hex
56
+ Utils.bin_to_prefixed_hex encoded
57
+ end
58
+
59
+ def sign(key)
60
+ self.signature = key.sign(unsigned_encoded)
61
+ vrs = Utils.v_r_s_for signature
62
+ self.v = vrs[0]
63
+ self.r = vrs[1]
64
+ self.s = vrs[2]
65
+
66
+ self
67
+ end
68
+
69
+ def to_h
70
+ hash_keys.inject({}) do |hash, field|
71
+ hash[field] = send field
72
+ hash
73
+ end
74
+ end
75
+
76
+ def from
77
+ if signature
78
+ public_key = OpenSsl.recover_compact(signature_hash, signature)
79
+ Utils.public_key_to_address(public_key) if public_key
80
+ end
81
+ end
82
+
83
+ def signature
84
+ return @signature if @signature
85
+ self.signature = [
86
+ Utils.int_to_base256(v),
87
+ Utils.zpad_int(r),
88
+ Utils.zpad_int(s),
89
+ ].join if [v, r, s].all?
90
+ end
91
+
92
+ def hash
93
+ "0x#{Utils.bin_to_hex Utils.keccak256_rlp(self)}"
94
+ end
95
+ alias_method :id, :hash
96
+
97
+ def data_hex
98
+ Utils.bin_to_prefixed_hex data_bin
99
+ end
100
+
101
+ def data_hex=(hex)
102
+ self.data_bin = Utils.hex_to_bin(hex)
103
+ end
104
+
105
+ def data
106
+ MoacEth.tx_data_hex? ? data_hex : data_bin
107
+ end
108
+
109
+ def data=(string)
110
+ MoacEth.tx_data_hex? ? self.data_hex=(string) : self.data_bin=(string)
111
+ end
112
+
113
+
114
+ private
115
+
116
+ def hash_keys
117
+ keys = self.class.serializable_fields.keys
118
+ keys.delete(:data_bin)
119
+ keys + [:data]
120
+ end
121
+
122
+ def check_transaction_validity
123
+ if [gas_price, gas_limit, value, nonce].max > UINT_MAX
124
+ raise InvalidTransaction, "Values way too high!"
125
+ elsif gas_limit < intrinsic_gas_used
126
+ raise InvalidTransaction, "Gas limit too low"
127
+ end
128
+ end
129
+
130
+ def intrinsic_gas_used
131
+ num_zero_bytes = data_bin.count(BYTE_ZERO)
132
+ num_non_zero_bytes = data_bin.size - num_zero_bytes
133
+
134
+ Gas::GTXCOST +
135
+ Gas::GTXDATAZERO * num_zero_bytes +
136
+ Gas::GTXDATANONZERO * num_non_zero_bytes
137
+ end
138
+
139
+ def signature_hash
140
+ Utils.keccak256 unsigned_encoded
141
+ end
142
+
143
+ def unsigned
144
+ Tx.new to_h.merge(v: MoacEth.chain_id, r: 0, s: 0)
145
+ end
146
+
147
+ def sedes
148
+ if MoacEth.prevent_replays? && !(MoacEth.replayable_v? v)
149
+ self.class
150
+ else
151
+ UnsignedTx
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+ UnsignedTx = Tx.exclude([:v, :r, :s])
158
+ end
@@ -0,0 +1,126 @@
1
+ module MoacEth
2
+ module Utils
3
+
4
+ extend self
5
+
6
+ def normalize_address(address)
7
+ if address.nil? || address == ''
8
+ ''
9
+ elsif address.size == 40
10
+ hex_to_bin address
11
+ elsif address.size == 42 && address[0..1] == '0x'
12
+ hex_to_bin address[2..-1]
13
+ else
14
+ address
15
+ end
16
+ end
17
+
18
+ def bin_to_hex(string)
19
+ RLP::Utils.encode_hex string
20
+ end
21
+
22
+ def hex_to_bin(string)
23
+ RLP::Utils.decode_hex remove_hex_prefix(string)
24
+ end
25
+
26
+ def base256_to_int(str)
27
+ RLP::Sedes.big_endian_int.deserialize str.sub(/\A(\x00)+/, '')
28
+ end
29
+
30
+ def int_to_base256(int)
31
+ RLP::Sedes.big_endian_int.serialize int
32
+ end
33
+
34
+ def v_r_s_for(signature)
35
+ [
36
+ signature[0].bytes[0],
37
+ Utils.base256_to_int(signature[1..32]),
38
+ Utils.base256_to_int(signature[33..65]),
39
+ ]
40
+ end
41
+
42
+ def prefix_hex(hex)
43
+ hex.match(/\A0x/) ? hex : "0x#{hex}"
44
+ end
45
+
46
+ def remove_hex_prefix(s)
47
+ s[0,2] == '0x' ? s[2..-1] : s
48
+ end
49
+
50
+ def bin_to_prefixed_hex(binary)
51
+ prefix_hex bin_to_hex(binary)
52
+ end
53
+
54
+ def public_key_to_address(hex)
55
+ bytes = hex_to_bin(hex)
56
+ address_bytes = Utils.keccak256(bytes[1..-1])[-20..-1]
57
+ format_address bin_to_prefixed_hex(address_bytes)
58
+ end
59
+
60
+ def sha256(x)
61
+ Digest::SHA256.digest x
62
+ end
63
+
64
+ def keccak256(x)
65
+ Digest::SHA3.new(256).digest(x)
66
+ end
67
+
68
+ def keccak512(x)
69
+ Digest::SHA3.new(512).digest(x)
70
+ end
71
+
72
+ def keccak256_rlp(x)
73
+ keccak256 RLP.encode(x)
74
+ end
75
+
76
+ def ripemd160(x)
77
+ Digest::RMD160.digest x
78
+ end
79
+
80
+ def hash160(x)
81
+ ripemd160 sha256(x)
82
+ end
83
+
84
+ def zpad(x, l)
85
+ lpad x, BYTE_ZERO, l
86
+ end
87
+
88
+ def zunpad(x)
89
+ x.sub /\A\x00+/, ''
90
+ end
91
+
92
+ def zpad_int(n, l=32)
93
+ zpad encode_int(n), l
94
+ end
95
+
96
+ def zpad_hex(s, l=32)
97
+ zpad decode_hex(s), l
98
+ end
99
+
100
+ def valid_address?(address)
101
+ Address.new(address).valid?
102
+ end
103
+
104
+ def format_address(address)
105
+ Address.new(address).checksummed
106
+ end
107
+
108
+
109
+
110
+ private
111
+
112
+ def lpad(x, symbol, l)
113
+ return x if x.size >= l
114
+ symbol * (l - x.size) + x
115
+ end
116
+
117
+ def encode_int(n)
118
+ unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
119
+ raise ArgumentError, "Integer invalid or out of range: #{n}"
120
+ end
121
+
122
+ int_to_base256 n
123
+ end
124
+
125
+ end
126
+ end
@@ -0,0 +1,3 @@
1
+ module MoacEth
2
+ VERSION = "0.4.7"
3
+ end
data/lib/moac_eth.rb ADDED
@@ -0,0 +1,75 @@
1
+ require 'digest/sha3'
2
+ require 'ffi'
3
+ require 'money-tree'
4
+ require 'rlp'
5
+
6
+ module MoacEth
7
+ BYTE_ZERO = "\x00".freeze
8
+ UINT_MAX = 2**256 - 1
9
+
10
+ autoload :Address, 'moac_eth/address'
11
+ autoload :Gas, 'moac_eth/gas'
12
+ autoload :Key, 'moac_eth/key'
13
+ autoload :OpenSsl, 'moac_eth/open_ssl'
14
+ autoload :Secp256k1, 'moac_eth/secp256k1'
15
+ autoload :Sedes, 'moac_eth/sedes'
16
+ autoload :Tx, 'moac_eth/tx'
17
+ autoload :Utils, 'moac_eth/utils'
18
+
19
+ class << self
20
+ def configure
21
+ yield(configuration)
22
+ end
23
+
24
+ def replayable_chain_id
25
+ 27
26
+ end
27
+
28
+ def chain_id
29
+ configuration.chain_id
30
+ end
31
+
32
+ def v_base
33
+ if chain_id
34
+ (chain_id * 2) + 35
35
+ else
36
+ replayable_chain_id
37
+ end
38
+ end
39
+
40
+ def prevent_replays?
41
+ !chain_id.nil?
42
+ end
43
+
44
+ def replayable_v?(v)
45
+ [replayable_chain_id, replayable_chain_id + 1].include? v
46
+ end
47
+
48
+ def tx_data_hex?
49
+ !!configuration.tx_data_hex
50
+ end
51
+
52
+
53
+ private
54
+
55
+ def configuration
56
+ @configuration ||= Configuration.new
57
+ end
58
+ end
59
+
60
+ class Configuration
61
+ attr_accessor :chain_id, :tx_data_hex
62
+
63
+ def initialize
64
+ self.tx_data_hex = true
65
+
66
+ # Moac chain
67
+ # Network ID for Testnet is 101
68
+ # Network ID for Mainnet is 99
69
+ self.chain_id = 101
70
+ end
71
+ end
72
+
73
+ class ValidationError < StandardError; end
74
+ class InvalidTransaction < ValidationError; end
75
+ end
data/moac_eth.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'moac_eth/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "moac_eth"
8
+ spec.version = MoacEth::VERSION
9
+ spec.authors = ["Steve Ellis"]
10
+ spec.email = ["email@steveell.is"]
11
+
12
+ spec.summary = %q{Simple API to sign Ethereum transactions.}
13
+ spec.description = %q{Library to build, parse, and sign Ethereum transactions.}
14
+ spec.homepage = "https://github.com/se3000/ruby-eth"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'digest-sha3', '~> 1.1'
23
+ spec.add_dependency 'ffi', '~> 1.0'
24
+ spec.add_dependency 'money-tree', '~> 0.9'
25
+ spec.add_dependency 'rlp', '~> 0.7.3'
26
+ spec.add_dependency 'scrypt', '~> 3.0.5'
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.12'
29
+ spec.add_development_dependency 'pry', '~> 0.1'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.0'
32
+ end