neb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "./request"
5
+ require_relative "./response"
6
+
7
+ module Neb
8
+ class Client
9
+ class API
10
+ attr_reader :host, :api_version, :endpoint
11
+
12
+ def initialize
13
+ @host = CONFIG[:host]
14
+ @api_version = CONFIG[:api_version]
15
+ @endpoint = CONFIG[:api_endpoint]
16
+ end
17
+
18
+ def get_neb_state
19
+ send_request(:get, "/nebstate")
20
+ end
21
+
22
+ def latest_irreversible_block
23
+ send_request(:get, "/lib")
24
+ end
25
+
26
+ def get_account_state(address: , height: 0)
27
+ send_request(:post, "/accountstate", address: address, height: height)
28
+ end
29
+
30
+ def call(from:, to:, value:, nonce:, gas_price: 1_000_000, gas_limit: 20_000, contract: nil)
31
+ params = {
32
+ from: from,
33
+ to: to,
34
+ value: value.to_i,
35
+ nonce: nonce.to_i,
36
+ gas_price: gas_price.to_i,
37
+ gas_limit: gas_limit.to_i,
38
+ contract: contract
39
+ }
40
+
41
+ send_request(:post, "/call", params)
42
+ end
43
+
44
+ def send_raw_transaction(data:)
45
+ send_request(:post, "/rawtransaction", data: data)
46
+ end
47
+
48
+ def get_block_by_hash(hash:, is_full: true)
49
+ send_request(:post, "/getBlockByHash", hash: hash, full_fill_transaction: is_full)
50
+ end
51
+
52
+ def get_block_by_height(height:, is_full: true)
53
+ send_request(:post, "/getBlockByHeight", height: height, full_fill_transaction: is_full)
54
+ end
55
+
56
+ def get_transaction_receipt(hash:)
57
+ send_request(:post, "/getTransactionReceipt", hash: hash)
58
+ end
59
+
60
+ def get_transaction_by_contract(address:)
61
+ send_request(:post, "/getTransactionByContract", address: address)
62
+ end
63
+
64
+ def subscribe(topics: [])
65
+ send_request(:post, "/subscribe", topics: topics)
66
+ end
67
+
68
+ def get_gas_price
69
+ send_request(:get, "/getGasPrice")
70
+ end
71
+ alias_method :gas_price, :get_gas_price
72
+
73
+ def estimate_gas(from:, to:, value:, nonce:, gas_price: 1_000_000, gas_limit: 20_000, contract: nil, binary: nil)
74
+ params = {
75
+ from: from,
76
+ to: to,
77
+ value: value.to_i,
78
+ nonce: nonce.to_i,
79
+ gas_price: gas_price.to_i,
80
+ gas_limit: gas_limit.to_i,
81
+ contract: contract,
82
+ binary: binary
83
+ }
84
+
85
+ send_request(:post, "/estimateGas", params)
86
+ end
87
+
88
+ def get_events_by_hash(hash:)
89
+ send_request(:post, "/getEventsByHash", hash: hash)
90
+ end
91
+
92
+ def get_dynasty(height:)
93
+ send_request(:post, "/dynasty", height: height)
94
+ end
95
+
96
+ private
97
+
98
+ def send_request(action, url, payload = {})
99
+ request_url = host + api_version + endpoint + url
100
+ Request.new(action, request_url, payload).execute
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class Client
6
+ class Request
7
+ attr_reader :action, :url, :payload
8
+
9
+ def initialize(action, url, payload = {}, headers = {})
10
+ @action = action
11
+ @url = url
12
+ @payload = payload
13
+ @headers = headers.blank? ? default_headers : headers
14
+ end
15
+
16
+ def to_s
17
+ "#{self.class}{action=#{action}, url=#{url}, payload=#{payload}}"
18
+ end
19
+
20
+ def request_args
21
+ {
22
+ method: action,
23
+ url: url,
24
+ payload: payload.deep_camelize_keys(:lower).to_json,
25
+ headers: { content_type: :json, accept: :json }
26
+ }
27
+ end
28
+
29
+ def execute
30
+ Neb.logger.debug(self.to_s)
31
+ ::RestClient::Request.execute(request_args) do |resp, _, _|
32
+ Response.new(resp)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def default_headers
39
+ { content_type: :json, accept: :json }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class Client
6
+ class Response
7
+ extend Forwardable
8
+
9
+ attr_reader :response
10
+ def_delegators :@response, :code, :body
11
+
12
+ SUCCESS_CODE = 200
13
+
14
+ def initialize(response)
15
+ @response = response
16
+ Neb.logger.debug(self.to_s)
17
+ end
18
+
19
+ def to_s
20
+ "#{self.class}{code=#{code}, body=#{body}}"
21
+ end
22
+
23
+ def result
24
+ JSON.parse(body, symbolize_names: true)[:result] if success?
25
+ end
26
+
27
+ def error
28
+ JSON.parse(body, symbolize_names: true)[:error] if !success?
29
+ end
30
+
31
+ def success?
32
+ code == SUCCESS_CODE
33
+ end
34
+ end
35
+ end
36
+ end
data/lib/neb/client.rb ADDED
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "./client/admin"
5
+ require_relative "./client/api"
6
+
7
+ module Neb
8
+ class Client
9
+ attr_reader :api, :admin
10
+
11
+ def initialize(config = {})
12
+ Neb.configure(config) unless Neb.configured?
13
+
14
+ @api = API.new
15
+ @admin = Admin.new
16
+ end
17
+ end
18
+ end
19
+
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class Configuration
6
+ extend Forwardable
7
+ def_delegators :@hash, :to_hash, :[], :[]=, :==, :fetch, :delete, :has_key?
8
+
9
+ DEFAULTS = {
10
+ host: "http://localhost:8685",
11
+ timeout: "0",
12
+ api_version: "/v1",
13
+ log: "log/neb.log",
14
+ api_endpoint: "/user",
15
+ admin_endpoint: "/admin"
16
+ }.freeze
17
+
18
+ def initialize
19
+ clear
20
+ end
21
+
22
+ def clear
23
+ @hash = DEFAULTS.dup
24
+ end
25
+
26
+ def merge!(hash)
27
+ hash = hash.dup
28
+ @hash = @hash.deep_merge(hash)
29
+ end
30
+
31
+ def merge(hash)
32
+ instance = self.class.new
33
+ instance.merge!(to_hash)
34
+ instance.merge!(hash)
35
+ instance
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ module Constant
6
+
7
+ BYTE_EMPTY = "".freeze
8
+ BYTE_ZERO = "\x00".freeze
9
+ BYTE_ONE = "\x01".freeze
10
+
11
+ TT32 = 2**32
12
+ TT40 = 2**40
13
+ TT160 = 2**160
14
+ TT256 = 2**256
15
+ TT64M1 = 2**64 - 1
16
+
17
+ UINT_MAX = 2**256 - 1
18
+ UINT_MIN = 0
19
+ INT_MAX = 2**255 - 1
20
+ INT_MIN = -2**255
21
+
22
+ HASH_ZERO = ("\x00"*32).freeze
23
+
24
+ PUBKEY_ZERO = ("\x00"*32).freeze
25
+ PRIVKEY_ZERO = ("\x00"*32).freeze
26
+ PRIVKEY_ZERO_HEX = ('0'*64).freeze
27
+
28
+ CONTRACT_CODE_SIZE_LIMIT = 0x6000
29
+
30
+ ADDRESS_LENGTH = 26
31
+ ADDRESS_PREFIX = 25
32
+ NORMAL_TYPE = 87
33
+ CONTRACT_TYPE = 88
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ class Hash
5
+ # Returns a new hash with all keys converted to UpperCamelCase strings.If th
6
+ # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
7
+ #
8
+ # hash = { my_name: 'Rob', my_age: '28' }
9
+ #
10
+ # hash.camelize_keys
11
+ # # => {"MyName"=>"Rob", "MyAge"=>"28"}
12
+ #
13
+ # hash.camelize_keys(:lower)
14
+ # # => {"myName"=>"Rob", "myAge"=>"28"}
15
+ def camelize_keys(first_letter = :upper)
16
+ transform_keys { |key| key.to_s.camelize(first_letter) rescue key }
17
+ end
18
+
19
+ # Destructively converts all keys to strings. Same as
20
+ # +camelize_keys+, but modifies +self+.
21
+ def camelize_keys!(first_letter = :upper)
22
+ transform_keys! { |key| key.to_s.camelize(first_letter) rescue key }
23
+ end
24
+
25
+ # Returns a new hash with all keys converted to UpperCamelCase strings.If th
26
+ # is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
27
+ # This includes the keys from the root hash
28
+ # and from all nested hashes and arrays.
29
+ #
30
+ # hash = { my_name: 'Rob', my_age: '28', my_friend: { his_name: "bob" } }
31
+ #
32
+ # hash.deep_camelize_keys
33
+ # # => {"MyName"=>"Rob", "MyAge"=>"28", "MyFriend"=>{"HisName"=>"bob"}}
34
+ def deep_camelize_keys(first_letter = :upper)
35
+ deep_transform_keys { |key| key.to_s.camelize(first_letter) rescue key }
36
+ end
37
+
38
+ # Destructively converts all keys to strings. Same as
39
+ # +camelize_keys+, but modifies +self+.
40
+ # This includes the keys from the root hash and from all
41
+ # nested hashes and arrays.
42
+ def deep_camelize_keys!(first_letter = :upper)
43
+ deep_transform_keys! { |key| key.to_s.camelize(first_letter) rescue key }
44
+ end
45
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class DeprecatedError < StandardError; end
6
+ class InvalidJSONKeyError < StandardError; end
7
+ class ChecksumError < StandardError; end
8
+ class FormatError < StandardError; end
9
+ class ValidationError < StandardError; end
10
+ class ValueError < StandardError; end
11
+ class AssertError < StandardError; end
12
+ class UnsignError < StandardError; end
13
+
14
+ class UnknownParentError < StandardError; end
15
+ class InvalidBlock < ValidationError; end
16
+ class InvalidUncles < ValidationError; end
17
+
18
+ class InvalidTransaction < ValidationError; end
19
+ class UnsignedTransactionError < InvalidTransaction; end
20
+ class InvalidNonce < InvalidTransaction; end
21
+ class InsufficientStartGas < InvalidTransaction; end
22
+ class InsufficientBalance < InvalidTransaction; end
23
+ class BlockGasLimitReached < InvalidTransaction; end
24
+
25
+ class InvalidSPVProof < ValidationError; end
26
+
27
+ class ContractCreationFailed < StandardError; end
28
+ class TransactionFailed < StandardError; end
29
+ end
data/lib/neb/key.rb ADDED
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class Key
6
+ KDF = "scrypt".freeze
7
+ CIPHER_NAME = "aes-128-ctr".freeze
8
+ MACHASH = "sha3256".freeze
9
+
10
+ KDF_N = 1 << 12
11
+ KDF_R = 8
12
+ KDF_P = 1
13
+ DKLEN = 32
14
+
15
+ KEY_CURRENT_VERSION = 4
16
+ KEY_VERSION_3 = 3
17
+
18
+ attr_accessor :address, :private_key, :password, :salt, :iv, :mac
19
+
20
+ def initialize(address: nil, private_key: nil, password: nil, salt: nil, iv: nil)
21
+ @address = Address.new(address) if address
22
+ @private_key = PrivateKey.new(private_key) if private_key
23
+ @password = password
24
+ @salt = self.class.convert_salt(salt || Utils.random_bytes(32))
25
+ @iv = self.class.convert_iv(iv || Utils.random_bytes(16))
26
+ end
27
+
28
+ def encrypt
29
+ derived_key = Utils.scrypt(password, salt, KDF_N, KDF_R, KDF_P, DKLEN)
30
+ ciphertext_bin = Utils.aes_encrypt(private_key.encode(:bin), derived_key[0, 16], iv)
31
+ mac_bin = Utils.keccak256([derived_key[16, 16], ciphertext_bin, iv, CIPHER_NAME].join)
32
+
33
+ {
34
+ version: KEY_CURRENT_VERSION,
35
+ id: Utils.uuid,
36
+ address: address.to_s,
37
+ crypto: {
38
+ ciphertext: Utils.bin_to_hex(ciphertext_bin),
39
+ cipherparams: {
40
+ iv: Utils.bin_to_hex(iv)
41
+ },
42
+ cipher: CIPHER_NAME,
43
+ kdf: KDF,
44
+ kdfparams: {
45
+ dklen: DKLEN,
46
+ salt: Utils.bin_to_hex(salt),
47
+ n: KDF_N,
48
+ r: KDF_R,
49
+ p: KDF_P
50
+ },
51
+ mac: Utils.bin_to_hex(mac_bin),
52
+ machash: MACHASH
53
+ }
54
+ }
55
+ end
56
+
57
+ class << self
58
+
59
+ def encrypt(address, private_key, password)
60
+ new(address: address, private_key: private_key, password: password).encrypt
61
+ end
62
+
63
+ def decrypt(key_data, password)
64
+ key_data = Utils.from_json(key_data) if key_data.is_a?(String)
65
+ key_data = key_data.deep_symbolize_keys!
66
+
67
+ raise InvalidJSONKeyError("key data validate failed") if !validate?(key_data)
68
+
69
+ version = key_data[:version]
70
+ address = key_data[:address]
71
+ crypto = key_data[:crypto]
72
+
73
+ cipher = crypto[:cipher]
74
+ salt = convert_salt(crypto[:kdfparams][:salt])
75
+ dklen = crypto[:kdfparams][:dklen]
76
+ kdf_n = crypto[:kdfparams][:n]
77
+ kdf_r = crypto[:kdfparams][:r]
78
+ kdf_p = crypto[:kdfparams][:p]
79
+ iv = convert_iv(crypto[:cipherparams][:iv])
80
+
81
+ derived_key = Utils.scrypt(password, salt, kdf_n, kdf_r, kdf_p, dklen)
82
+ ciphertext_bin = Utils.hex_to_bin(crypto[:ciphertext])
83
+
84
+ if version == KEY_CURRENT_VERSION
85
+ mac = Utils.keccak256([derived_key[16, 16], ciphertext_bin, iv, cipher].join)
86
+ else
87
+ mac = Utils.keccak256([derived_key[16, 16], ciphertext_bin].join) # KeyVersion3
88
+ end
89
+
90
+ raise InvalidJSONKeyError.new("mac is wrong") if Utils.bin_to_hex(mac) != crypto[:mac]
91
+
92
+ private_key = Utils.aes_decrypt(ciphertext_bin, derived_key[0, 16], iv)
93
+ Utils.bin_to_hex(private_key)
94
+ end
95
+
96
+ def validate?(key_data)
97
+ key_data = Utils.from_json(key_data) if key_data.is_a?(String)
98
+
99
+ [:version, :id, :address, :crypto].each do |k|
100
+ return false if !key_data.keys.include?(k)
101
+ end
102
+
103
+ return false if key_data[:version] != KEY_CURRENT_VERSION && key_data[:version] != KEY_VERSION_3
104
+
105
+ [:ciphertext, :cipherparams, :cipher, :kdf, :kdfparams, :mac, :machash].each do |k|
106
+ return false if !key_data[:crypto].keys.include?(k)
107
+ end
108
+
109
+ return false if !key_data[:crypto][:cipherparams].keys.include?(:iv)
110
+
111
+ [:dklen, :salt, :n, :r, :p].each do |k|
112
+ return false if !key_data[:crypto][:kdfparams].keys.include?(k)
113
+ end
114
+
115
+ true
116
+ end
117
+
118
+ def convert_salt(salt)
119
+ return salt if salt.length == 32
120
+ return Utils.hex_to_bin(salt) if salt.length == 64
121
+
122
+ raise ArgumentError.new("salt must be 32 bytes")
123
+ end
124
+
125
+ def convert_iv(iv)
126
+ return iv if iv.length == 16
127
+ return Utils.hex_to_bin(iv) if iv.length == 32
128
+
129
+ raise ArgumentError.new("iv must be 16 bytes")
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,91 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class PrivateKey
6
+ attr_reader :raw
7
+
8
+ def initialize(raw)
9
+ @raw = raw
10
+ end
11
+
12
+ def to_s
13
+ encode(:hex)
14
+ end
15
+
16
+ def encode(fmt)
17
+ return self.class.new(value).encode(fmt) unless raw.is_a?(Numeric)
18
+
19
+ case fmt
20
+ when :decimal
21
+ raw
22
+ when :bin
23
+ BaseConvert.encode(raw, 256, 32)
24
+ when :bin_compressed
25
+ "#{BaseConvert.encode(raw, 256, 32)}\x01"
26
+ when :hex
27
+ BaseConvert.encode(raw, 16, 64)
28
+ when :hex_compressed
29
+ "#{BaseConvert.encode(raw, 16, 64)}01"
30
+ else
31
+ raise ArgumentError, "invalid format: #{fmt}"
32
+ end
33
+ end
34
+
35
+ def decode(fmt = nil)
36
+ fmt ||= format
37
+
38
+ case fmt
39
+ when :decimal
40
+ raw
41
+ when :bin
42
+ BaseConvert.decode(raw, 256)
43
+ when :bin_compressed
44
+ BaseConvert.decode(raw[0, 32], 256)
45
+ when :hex
46
+ BaseConvert.decode(raw, 16)
47
+ when :hex_compressed
48
+ BaseConvert.decode(raw[0, 64], 16)
49
+ else
50
+ raise ArgumentError, "WIF does not represent privkey"
51
+ end
52
+ end
53
+
54
+ def value
55
+ @value ||= decode
56
+ end
57
+
58
+ def format
59
+ return :decimal if raw.is_a?(Numeric)
60
+ return :bin if raw.size == 32
61
+ return :bin_compressed if raw.size == 33
62
+ return :hex if raw.size == 64
63
+ return :hex_compressed if raw.size == 66
64
+ end
65
+
66
+ def to_pubkey
67
+ to_pubkey_obj.to_s
68
+ end
69
+
70
+ def to_pubkey_obj
71
+ raise ValidationError, "Invalid private key" if value >= Secp256k1::N
72
+ PublicKey.new(Secp256k1.priv_to_pub(encode(:bin)))
73
+ end
74
+
75
+ def to_address_obj
76
+ PublicKey.new(to_pubkey_obj.encode(:bin)).to_address
77
+ end
78
+
79
+ def to_address
80
+ to_address_obj.to_s
81
+ end
82
+
83
+ class << self
84
+
85
+ def random
86
+ new(Utils.random_bytes)
87
+ end
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,40 @@
1
+ // Copyright (C) 2017 go-nebulas authors
2
+ //
3
+ // This file is part of the go-nebulas library.
4
+ //
5
+ // the go-nebulas library is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // the go-nebulas library is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU General Public License
16
+ // along with the go-nebulas library. If not, see <http://www.gnu.org/licenses/>.
17
+ //
18
+ syntax = "proto3";
19
+ package corepb;
20
+
21
+ message Data {
22
+ string type = 1;
23
+ bytes payload = 2;
24
+ }
25
+
26
+ message Transaction {
27
+ bytes hash = 1;
28
+ bytes from = 2;
29
+ bytes to = 3;
30
+ bytes value = 4;
31
+ uint64 nonce = 5;
32
+ int64 timestamp = 6;
33
+ Data data = 7;
34
+ uint32 chain_id = 8;
35
+ bytes gas_price = 9;
36
+ bytes gas_limit = 10;
37
+
38
+ uint32 alg = 11;
39
+ bytes sign = 12;
40
+ }
@@ -0,0 +1,30 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: lib/neb/proto/transaction.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_message "corepb.Data" do
8
+ optional :type, :string, 1
9
+ optional :payload, :bytes, 2
10
+ end
11
+ add_message "corepb.Transaction" do
12
+ optional :hash, :bytes, 1
13
+ optional :from, :bytes, 2
14
+ optional :to, :bytes, 3
15
+ optional :value, :bytes, 4
16
+ optional :nonce, :uint64, 5
17
+ optional :timestamp, :int64, 6
18
+ optional :data, :message, 7, "corepb.Data"
19
+ optional :chain_id, :uint32, 8
20
+ optional :gas_price, :bytes, 9
21
+ optional :gas_limit, :bytes, 10
22
+ optional :alg, :uint32, 11
23
+ optional :sign, :bytes, 12
24
+ end
25
+ end
26
+
27
+ module Corepb
28
+ Data = Google::Protobuf::DescriptorPool.generated_pool.lookup("corepb.Data").msgclass
29
+ Transaction = Google::Protobuf::DescriptorPool.generated_pool.lookup("corepb.Transaction").msgclass
30
+ end