neb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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