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,87 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class PublicKey
6
+ attr_reader :raw
7
+
8
+ def initialize(raw)
9
+ @raw = raw
10
+ end
11
+
12
+ # skip the type flag, 04
13
+ def to_s
14
+ encode(:hex)[2..-1]
15
+ end
16
+
17
+ def encode(fmt)
18
+ case fmt
19
+ when :decimal
20
+ value
21
+ when :bin
22
+ "\x04#{BaseConvert.encode(value[0], 256, 32)}#{BaseConvert.encode(value[1], 256, 32)}"
23
+ when :bin_compressed
24
+ "#{(2+(value[1]%2)).chr}#{BaseConvert.encode(value[0], 256, 32)}"
25
+ when :hex
26
+ "04#{BaseConvert.encode(value[0], 16, 64)}#{BaseConvert.encode(value[1], 16, 64)}"
27
+ when :hex_compressed
28
+ "0#{2+(value[1]%2)}#{BaseConvert.encode(value[0], 16, 64)}"
29
+ else
30
+ raise FormatError, "Invalid format!"
31
+ end
32
+ end
33
+
34
+ def decode(fmt = nil)
35
+ fmt ||= format
36
+
37
+ case fmt
38
+ when :decimal
39
+ raw
40
+ when :bin
41
+ [BaseConvert.decode(raw[1, 32], 256), BaseConvert.decode(raw[33, 32], 256)]
42
+ when :bin_compressed
43
+ x = BaseConvert.decode raw[1, 32], 256
44
+ m = x*x*x + Secp256k1::A*x + Secp256k1::B
45
+ n = Utils.mod_exp(m, (Secp256k1::P+1)/4, Secp256k1::P)
46
+ q = (n + raw[0].ord) % 2
47
+ y = q == 1 ? (Secp256k1::P - n) : n
48
+ [x, y]
49
+ when :hex
50
+ [BaseConvert.decode(raw[2, 64], 16), BaseConvert.decode(raw[66, 64], 16)]
51
+ when :hex_compressed
52
+ PublicKey.new(Utils.hex_to_bin(raw)).decode :bin_compressed
53
+ else
54
+ raise FormatError, "Invalid format!"
55
+ end
56
+ end
57
+
58
+ def value
59
+ @value ||= decode
60
+ end
61
+
62
+ def format
63
+ return :decimal if raw.is_a?(Array)
64
+ return :bin if raw.size == 65 && raw[0] == "\x04"
65
+ return :hex if raw.size == 130 && raw[0, 2] == '04'
66
+ return :bin_compressed if raw.size == 33 && "\x02\x03".include?(raw[0])
67
+ return :hex_compressed if raw.size == 66 && %w(02 03).include?(raw[0,2])
68
+
69
+ raise FormatError, "Pubkey is not in recognized format"
70
+ end
71
+
72
+ def to_address_obj
73
+ bytes = [
74
+ BaseConvert.encode(Constant::ADDRESS_PREFIX, 256, 1),
75
+ BaseConvert.encode(Constant::NORMAL_TYPE, 256, 1),
76
+ Utils.hash160(encode(:bin))
77
+ ].join
78
+
79
+ Address.new(bytes)
80
+ end
81
+
82
+ def to_address
83
+ to_address_obj.to_s
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ module Secp256k1
6
+
7
+ # Elliptic curve parameters
8
+ P = 2**256 - 2**32 - 977
9
+ N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
10
+ A = 0
11
+ B = 7
12
+ Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
13
+ Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
14
+ G = [Gx, Gy].freeze
15
+
16
+ SECP256K1 = 1
17
+
18
+ class InvalidPrivateKey < StandardError; end
19
+
20
+ class << self # extensions
21
+
22
+ def sign(msg, priv)
23
+ priv = PrivateKey.new(priv)
24
+ privkey = ::Secp256k1::PrivateKey.new(privkey: priv.encode(:bin), raw: true)
25
+ sig_raw = privkey.ecdsa_sign(msg, raw: true)
26
+ privkey.ecdsa_serialize_compact(sig_raw) << SECP256K1
27
+ end
28
+
29
+ def priv_to_pub(priv)
30
+ priv = PrivateKey.new(priv)
31
+ privkey = ::Secp256k1::PrivateKey.new(privkey: priv.encode(:bin), raw: true)
32
+ pubkey = privkey.pubkey
33
+ PublicKey.new(pubkey.serialize).encode(priv.format)
34
+ end
35
+
36
+ def recoverable_sign(msg, privkey)
37
+ pk = ::Secp256k1::PrivateKey.new(privkey: privkey, raw: true)
38
+ signature = pk.ecdsa_recoverable_serialize(pk.ecdsa_sign_recoverable(msg, raw: true))
39
+
40
+ v = signature[1]
41
+ r = Utils.big_endian_to_int signature[0][0,32]
42
+ s = Utils.big_endian_to_int signature[0][32,32]
43
+
44
+ [v,r,s]
45
+ end
46
+
47
+ def signature_verify(msg, vrs, pubkey)
48
+ pk = ::Secp256k1::PublicKey.new(pubkey: pubkey)
49
+ raw_sig = Utils.zpad_int(vrs[1]) + Utils.zpad_int(vrs[2])
50
+
51
+ sig = ::Secp256k1::C::ECDSASignature.new
52
+ sig[:data].to_ptr.write_bytes(raw_sig)
53
+
54
+ pk.ecdsa_verify(msg, sig)
55
+ end
56
+
57
+ def recover_pubkey(msg, vrs, compressed: false)
58
+ pk = ::Secp256k1::PublicKey.new(flags: ::Secp256k1::ALL_FLAGS)
59
+ sig = Utils.zpad_int(vrs[1]) + Utils.zpad_int(vrs[2])
60
+ recsig = pk.ecdsa_recoverable_deserialize(sig, vrs[0])
61
+ pk.public_key = pk.ecdsa_recover msg, recsig, raw: true
62
+ pk.serialize compressed: compressed
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ class Transaction
6
+
7
+ MAX_GAS_PRICE = 1_000_000_000_000
8
+ MAX_GAS = 50_000_000_000
9
+ GAS_PRICE = 1_000_000
10
+ GAS_LIMIT = 20_000
11
+
12
+ PAYLOAD_BINARY_TYPE = "binary".freeze
13
+ PAYLOAD_DEPLOY_TYPE = "deploy".freeze
14
+ PAYLOAD_CALL_TYPE = "call".freeze
15
+
16
+ CHAIN_ID_LIST = {
17
+ 1 => { name: "Mainnet", url: "https://mainnet.nebulas.io" },
18
+ 1001 => { name: "Testnet", url: "https://testnet.nebulas.io" },
19
+ 100 => { name: "Local Nodes", url: "http://127.0.0.1:8685" }
20
+ }.freeze
21
+
22
+ attr_reader :chain_id, :from_account, :to_address, :value, :nonce,
23
+ :gas_price, :gas_limit, :timestamp, :data
24
+ attr_reader :hash, :sign
25
+
26
+ def initialize(chain_id:, from_account:, to_address:, value:, nonce:,
27
+ gas_price: GAS_PRICE, gas_limit: GAS_LIMIT, contract: {})
28
+ @chain_id = chain_id
29
+ @from_account = from_account
30
+ @to_address = Address.new(to_address)
31
+ @value = value
32
+ @nonce = nonce
33
+ @gas_price = gas_price
34
+ @gas_limit = gas_limit
35
+ @data = parse_contract(contract)
36
+ @timestamp = Time.now.to_i
37
+ end
38
+
39
+ def parse_contract(contract)
40
+ payload_type, payload = nil, nil
41
+ contract.deep_symbolize_keys!
42
+
43
+ if contract[:source].present?
44
+ payload_type = PAYLOAD_DEPLOY_TYPE
45
+ payload = {
46
+ source_type: contract[:source_type],
47
+ source: contract[:source],
48
+ args: contract[:args]
49
+ }
50
+ elsif contract[:function].present?
51
+ payload_type = PAYLOAD_CALL_TYPE
52
+ payload = {
53
+ function: contract[:function],
54
+ args: contract[:args]
55
+ }
56
+ else
57
+ payload_type = PAYLOAD_BINARY_TYPE
58
+ if contract.present?
59
+ payload = {
60
+ data: BaseConvert.decode(contract[:binary], 256)
61
+ }
62
+ end
63
+ end
64
+
65
+ if payload.present?
66
+ payload = String.new(JSON.dump(payload.deep_camelize_keys(:upper)).html_safe)
67
+ { type: payload_type, payload: payload }
68
+ else
69
+ { type: payload_type }
70
+ end
71
+ end
72
+
73
+ def to_proto
74
+ raise UnsignError.new("Must sign_hash first") if sign.blank?
75
+
76
+ tx = Corepb::Transaction.new(
77
+ hash: hash,
78
+ from: from_account.address_obj.encode(:bin_extended),
79
+ to: to_address.encode(:bin_extended),
80
+ value: Utils.zpad(Utils.int_to_big_endian(value), 16),
81
+ nonce: nonce,
82
+ timestamp: timestamp,
83
+ data: Corepb::Data.new(data),
84
+ chain_id: chain_id,
85
+ gas_price: Utils.zpad(Utils.int_to_big_endian(gas_price), 16),
86
+ gas_limit: Utils.zpad(Utils.int_to_big_endian(gas_limit), 16),
87
+ alg: Secp256k1::SECP256K1,
88
+ sign: sign
89
+ )
90
+
91
+ tx.to_proto
92
+ end
93
+
94
+ def to_proto_str
95
+ Utils.encode64(to_proto)
96
+ end
97
+
98
+ def calculate_hash
99
+ buffer = [
100
+ from_account.address_obj.encode(:bin_extended),
101
+ to_address.encode(:bin_extended),
102
+ Utils.zpad(Utils.int_to_big_endian(value), 16),
103
+ Utils.zpad(Utils.int_to_big_endian(nonce), 8),
104
+ Utils.zpad(Utils.int_to_big_endian(timestamp), 8),
105
+ Corepb::Data.new(data).to_proto,
106
+ Utils.zpad(Utils.int_to_big_endian(chain_id), 4),
107
+ Utils.zpad(Utils.int_to_big_endian(gas_price), 16),
108
+ Utils.zpad(Utils.int_to_big_endian(gas_limit), 16)
109
+ ].join
110
+
111
+ Utils.keccak256(buffer)
112
+ end
113
+
114
+ def sign_hash
115
+ @hash = calculate_hash
116
+ @sign = Secp256k1.sign(@hash, from_account.private_key)
117
+ end
118
+
119
+ end
120
+ end
data/lib/neb/utils.rb ADDED
@@ -0,0 +1,128 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ module Utils
6
+ extend self
7
+
8
+ include Constant
9
+
10
+ def to_json(h)
11
+ JSON.generate(h)
12
+ end
13
+
14
+ def from_json(s)
15
+ JSON.parse(s, symbolize_names: true)
16
+ end
17
+
18
+ def secure_compare(a, b)
19
+ ActiveSupport::SecurityUtils.secure_compare(a, b)
20
+ end
21
+
22
+ # args: n, r, p, key_len
23
+ def scrypt(secret, salt, *args)
24
+ SCrypt::Engine.scrypt(secret, salt, *args)
25
+ end
26
+
27
+ def aes_encrypt(raw, bin_key, bin_iv)
28
+ cipher = OpenSSL::Cipher::AES128.new(:ctr)
29
+ cipher.encrypt
30
+ cipher.key = bin_key
31
+ cipher.iv = bin_iv
32
+
33
+ result = cipher.update(raw)
34
+ result += cipher.final
35
+ result
36
+ end
37
+
38
+ def aes_decrypt(ciphertext, bin_key, bin_iv)
39
+ cipher = OpenSSL::Cipher::AES128.new(:ctr)
40
+ cipher.decrypt
41
+ cipher.key = bin_key
42
+ cipher.iv = bin_iv
43
+
44
+ result = cipher.update(ciphertext)
45
+ result += cipher.final
46
+ result
47
+ end
48
+
49
+ def uuid
50
+ SecureRandom.uuid
51
+ end
52
+
53
+ def encode64(str)
54
+ Base64.strict_encode64(str)
55
+ end
56
+
57
+ def big_endian_to_int(s)
58
+ RLP::Sedes.big_endian_int.deserialize(s.sub(/\A(\x00)+/, ''))
59
+ end
60
+
61
+ def int_to_big_endian(n)
62
+ RLP::Sedes.big_endian_int.serialize(n)
63
+ end
64
+
65
+ def bin_to_hex(bytes)
66
+ BaseConvert.convert(bytes, 256, 16, bytes.size * 2).force_encoding('utf-8')
67
+ end
68
+
69
+ def hex_to_bin(hex)
70
+ BaseConvert.convert(hex, 16, 256, hex.size / 2).force_encoding('ascii-8bit')
71
+ end
72
+
73
+ def random_bytes(size = 32)
74
+ SecureRandom.random_bytes(size)
75
+ end
76
+
77
+ def keccak256(x)
78
+ SHA3::Digest::SHA256.digest(x)
79
+ end
80
+
81
+ def keccak512(x)
82
+ SHA3::Digest::SHA512.digest(x)
83
+ end
84
+
85
+ def sha256(x)
86
+ Digest::SHA256.digest(x)
87
+ end
88
+
89
+ def ripemd160(x)
90
+ Digest::RMD160.digest(x)
91
+ end
92
+
93
+ def hash160(x)
94
+ ripemd160(keccak256(x))
95
+ end
96
+
97
+ def base58(x)
98
+ Base58.binary_to_base58(x, :bitcoin)
99
+ end
100
+ alias_method :binary_to_base58, :base58
101
+
102
+ def base58_to_binary(b)
103
+ Base58.base58_to_binary(b, :bitcoin)
104
+ end
105
+
106
+ def lpad(x, symbol, l)
107
+ return x if x.size >= l
108
+ symbol * (l - x.size) + x
109
+ end
110
+
111
+ def rpad(x, symbol, l)
112
+ return x if x.size >= l
113
+ x + symbol * (l - x.size)
114
+ end
115
+
116
+ def zpad(x, l)
117
+ lpad(x, BYTE_ZERO, l)
118
+ end
119
+
120
+ def mod_exp(x, y, n)
121
+ x.to_bn.mod_exp(y, n).to_i
122
+ end
123
+
124
+ def mod_mul(x, y, n)
125
+ x.to_bn.mod_mul(y, n).to_i
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module Neb
5
+ VERSION = "0.1.0".freeze
6
+ end
data/lib/neb.rb ADDED
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require "logger"
5
+ require "pathname"
6
+ require "digest"
7
+ require "sha3"
8
+ require "openssl"
9
+ require "base58"
10
+ require "securerandom"
11
+ require "scrypt"
12
+ require "active_support/all"
13
+ require "json"
14
+ require "forwardable"
15
+ require "rest-client"
16
+ require "secp256k1"
17
+ require "rlp"
18
+ require "google/protobuf"
19
+ require "base64"
20
+
21
+ require "neb/version"
22
+ require "neb/exceptions"
23
+ require "neb/constant"
24
+ require "neb/utils"
25
+ require "neb/core_ext"
26
+ require "neb/configuration"
27
+ require "neb/base_convert"
28
+ require "neb/client"
29
+ require "neb/secp256k1"
30
+ require "neb/private_key"
31
+ require "neb/public_key"
32
+ require "neb/address"
33
+ require "neb/account"
34
+ require "neb/key"
35
+ require "neb/transaction"
36
+ require "neb/proto/transaction_pb"
37
+
38
+ module Neb
39
+ extend self
40
+ CONFIG = Configuration.new
41
+
42
+ attr_reader :configured, :logger
43
+ alias_method :configured?, :configured
44
+
45
+ def configure(config = {})
46
+ CONFIG.merge!(config)
47
+ setup_general_logger!
48
+ @configured = true
49
+ end
50
+
51
+ def clear!
52
+ CONFIG.clear
53
+ @logger = nil
54
+ @configured = false
55
+ end
56
+
57
+ def root
58
+ Pathname.new(File.expand_path('../..', __FILE__))
59
+ end
60
+
61
+ private
62
+
63
+ def setup_general_logger!
64
+ if [:info, :debug, :error, :warn].all?{ |meth| CONFIG[:log].respond_to?(meth) }
65
+ @logger = CONFIG[:log]
66
+ else
67
+ @logger = ::Logger.new(CONFIG[:log])
68
+ @logger.formatter = ::Logger::Formatter.new
69
+ end
70
+ end
71
+ end
data/neb.gemspec ADDED
@@ -0,0 +1,40 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "neb/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "neb"
8
+ spec.version = Neb::VERSION
9
+ spec.authors = ["Spirit"]
10
+ spec.email = ["neverlandxy.naix@gmail.com"]
11
+
12
+ spec.summary = %q{the Nebulas compatible Ruby API}
13
+ spec.description = %q{the Nebulas compatible Ruby API}
14
+ spec.homepage = "https://github.com/NaixSpirit/neb.rb"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.16"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "pry", "~> 0.11"
28
+ spec.add_development_dependency "guard", "~> 2.14"
29
+ spec.add_development_dependency "guard-minitest", "~> 2.4"
30
+
31
+ spec.add_dependency "rest-client", "~> 2.0"
32
+ spec.add_dependency "activesupport", "~> 5"
33
+ spec.add_dependency "rlp", "~> 0.7.3"
34
+ spec.add_dependency "bitcoin-secp256k1", "~> 0.4"
35
+ spec.add_dependency "ffi", "~> 1.9"
36
+ spec.add_dependency "sha3", "~> 1.0"
37
+ spec.add_dependency "base58", "~> 0.2"
38
+ spec.add_dependency "scrypt", "~> 3.0"
39
+ spec.add_dependency "google-protobuf", "~> 3.5"
40
+ end
data/tmp/.keep ADDED
File without changes