moac_eth 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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