neb 0.1.0

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,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