money-tree-extended 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module MoneyTree
5
+ module Support
6
+ include OpenSSL
7
+
8
+ INT32_MAX = 256 ** [1].pack("L*").size
9
+ INT64_MAX = 256 ** [1].pack("Q*").size
10
+ BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
11
+
12
+ def int_to_base58(int_val, leading_zero_bytes=0)
13
+ base58_val, base = '', BASE58_CHARS.size
14
+ while int_val > 0
15
+ int_val, remainder = int_val.divmod(base)
16
+ base58_val = BASE58_CHARS[remainder] + base58_val
17
+ end
18
+ base58_val
19
+ end
20
+
21
+ def base58_to_int(base58_val)
22
+ int_val, base = 0, BASE58_CHARS.size
23
+ base58_val.reverse.each_char.with_index do |char,index|
24
+ raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = BASE58_CHARS.index(char)
25
+ int_val += char_index*(base**index)
26
+ end
27
+ int_val
28
+ end
29
+
30
+ def encode_base58(hex)
31
+ leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
32
+ ("1"*leading_zero_bytes) + int_to_base58( hex.to_i(16) )
33
+ end
34
+
35
+ def decode_base58(base58_val)
36
+ s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
37
+ s = '' if s == '00'
38
+ leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
39
+ s = ("00"*leading_zero_bytes) + s if leading_zero_bytes > 0
40
+ s
41
+ end
42
+ alias_method :base58_to_hex, :decode_base58
43
+
44
+ def to_serialized_base58(hex)
45
+ hash = sha256 hex
46
+ hash = sha256 hash
47
+ checksum = hash.slice(0..7)
48
+ address = hex + checksum
49
+ encode_base58 address
50
+ end
51
+
52
+ def from_serialized_base58(base58)
53
+ hex = decode_base58 base58
54
+ checksum = hex.slice!(-8..-1)
55
+ compare_checksum = sha256(sha256(hex)).slice(0..7)
56
+ raise EncodingError unless checksum == compare_checksum
57
+ hex
58
+ end
59
+
60
+ def digestify(digest_type, source, opts = {})
61
+ source = [source].pack("H*") unless opts[:ascii]
62
+ bytes_to_hex Digest.digest(digest_type, source)
63
+ end
64
+
65
+ def sha256(source, opts = {})
66
+ digestify('SHA256', source, opts)
67
+ end
68
+
69
+ def ripemd160(source, opts = {})
70
+ digestify('RIPEMD160', source, opts)
71
+ end
72
+
73
+ def encode_base64(hex)
74
+ Base64.encode64([hex].pack("H*")).chomp
75
+ end
76
+
77
+ def decode_base64(base64)
78
+ Base64.decode64(base64).unpack("H*")[0]
79
+ end
80
+
81
+ def hmac_sha512(key, message)
82
+ digest = Digest::SHA512.new
83
+ HMAC.digest digest, key, message
84
+ end
85
+
86
+ def hmac_sha512_hex(key, message)
87
+ md = hmac_sha512(key, message)
88
+ md.unpack("H*").first.rjust(64, '0')
89
+ end
90
+
91
+ def bytes_to_int(bytes, base = 16)
92
+ if bytes.is_a?(Array)
93
+ bytes = bytes.pack("C*")
94
+ end
95
+ bytes.unpack("H*")[0].to_i(16)
96
+ end
97
+
98
+ def int_to_hex(i, size=nil)
99
+ hex = i.to_s(16).downcase
100
+ if (hex.size % 2) != 0
101
+ hex = "#{0}#{hex}"
102
+ end
103
+
104
+ if size
105
+ hex.rjust(size, "0")
106
+ else
107
+ hex
108
+ end
109
+ end
110
+
111
+ def int_to_bytes(i)
112
+ [int_to_hex(i)].pack("H*")
113
+ end
114
+
115
+ def bytes_to_hex(bytes)
116
+ bytes.unpack("H*")[0].downcase
117
+ end
118
+
119
+ def hex_to_bytes(hex)
120
+ [hex].pack("H*")
121
+ end
122
+
123
+ def hex_to_int(hex)
124
+ hex.to_i(16)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ module MoneyTree
2
+ VERSION = "0.11.0"
3
+ end
data/lib/money-tree.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "openssl_extensions"
2
+ require "money-tree/version"
3
+ require "money-tree/support"
4
+ require "money-tree/networks"
5
+ require "money-tree/key"
6
+ require "money-tree/address"
7
+ require "money-tree/networks"
8
+ require "money-tree/node"
9
+
10
+ module MoneyTree
11
+
12
+ end
@@ -0,0 +1,73 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'openssl'
4
+ require 'ffi'
5
+
6
+ module MoneyTree
7
+ module OpenSSLExtensions
8
+ extend FFI::Library
9
+ ffi_lib ['libssl.so.1.0.0', 'libssl.so.10', 'libssl1.0.0', 'ssl']
10
+
11
+ NID_secp256k1 = 714
12
+ POINT_CONVERSION_COMPRESSED = 2
13
+ POINT_CONVERSION_UNCOMPRESSED = 4
14
+
15
+ attach_function :EC_KEY_free, [:pointer], :int
16
+ attach_function :EC_KEY_get0_group, [:pointer], :pointer
17
+ attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
18
+ attach_function :EC_POINT_clear_free, [:pointer], :int
19
+ attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
20
+ attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string
21
+ attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer
22
+ attach_function :EC_POINT_new, [:pointer], :pointer
23
+
24
+ def self.add(point_0, point_1)
25
+ validate_points(point_0, point_1)
26
+ eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
27
+ group = EC_KEY_get0_group(eckey)
28
+
29
+ point_0_hex = point_0.to_bn.to_s(16)
30
+ point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil)
31
+ point_1_hex = point_1.to_bn.to_s(16)
32
+ point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil)
33
+
34
+ sum_point = EC_POINT_new(group)
35
+ success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil)
36
+ hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil)
37
+
38
+ EC_KEY_free(eckey)
39
+ EC_POINT_clear_free(sum_point)
40
+ EC_POINT_clear_free(point_0_pt)
41
+ EC_POINT_clear_free(point_1_pt)
42
+
43
+ eckey = nil
44
+ group = nil
45
+ sum_point = nil
46
+ point_0_pt = nil
47
+ point_1_pt = nil
48
+
49
+ hex
50
+ end
51
+
52
+ def self.validate_points(*points)
53
+ points.each do |point|
54
+ if !point.is_a?(OpenSSL::PKey::EC::Point)
55
+ raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
56
+ elsif point.infinity?
57
+ raise ArgumentError, "point must not be infinity"
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ class OpenSSL::PKey::EC::Point
66
+ include MoneyTree::OpenSSLExtensions
67
+
68
+ def add(point)
69
+ sum_point_hex = MoneyTree::OpenSSLExtensions.add(self, point)
70
+ self.class.new group, OpenSSL::BN.new(sum_point_hex, 16)
71
+ end
72
+
73
+ end
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'money-tree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "money-tree-extended"
8
+ spec.version = MoneyTree::VERSION
9
+ spec.authors = ["Micah Winkelspecht", "Nikhar Ramchunder"]
10
+ spec.email = ["winkelspecht@gmail.com"]
11
+ spec.description = %q{A Ruby Gem implementation of Bitcoin HD Wallets (Extended)}
12
+ spec.summary = %q{Bitcoin Hierarchical Deterministic Wallets in Ruby! (Bitcoin standard BIP0032)}
13
+ spec.homepage = "https://github.com/nikharR/money-tree"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # # used with gem install ... -P HighSecurity
22
+ # spec.cert_chain = ["certs/mattatgemco.pem"]
23
+ # # Sign gem when evaluating spec with `gem` command
24
+ # # unless ENV has set a SKIP_GEM_SIGNING
25
+ # if ($0 =~ /gem\z/) and not ENV.include?("SKIP_GEM_SIGNING")
26
+ # spec.signing_key = File.join(Gem.user_home, ".ssh", "gem-private_key.pem")
27
+ # end
28
+
29
+
30
+ spec.add_dependency "ffi"
31
+
32
+ spec.add_development_dependency "bundler", "~> 1.3"
33
+ spec.add_development_dependency "rake"
34
+ spec.add_development_dependency "rspec"
35
+ spec.add_development_dependency "simplecov"
36
+ spec.add_development_dependency "coveralls"
37
+ spec.add_development_dependency "pry"
38
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe MoneyTree::Address do
4
+ describe "initialize" do
5
+ it "generates a private key by default" do
6
+ address = MoneyTree::Address.new
7
+ expect(address.private_key.key.length).to eql(64)
8
+ end
9
+
10
+ it "generates a public key by default" do
11
+ address = MoneyTree::Address.new
12
+ expect(address.public_key.key.length).to eql(66)
13
+ end
14
+
15
+ it "imports a private key in hex form" do
16
+ address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
17
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
18
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
19
+ expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
20
+ expect(address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
21
+ expect(address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
22
+ end
23
+
24
+ it "imports a private key in compressed wif format" do
25
+ address = MoneyTree::Address.new private_key: "KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK"
26
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
27
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
28
+ expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
29
+ end
30
+
31
+ it "imports a private key in uncompressed wif format" do
32
+ address = MoneyTree::Address.new private_key: "5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa"
33
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
34
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
35
+ end
36
+ end
37
+
38
+ describe "to_s" do
39
+ before do
40
+ @address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
41
+ end
42
+
43
+ it "returns compressed base58 public key" do
44
+ expect(@address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
45
+ expect(@address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
46
+ end
47
+
48
+ it "returns compressed WIF private key" do
49
+ expect(@address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
50
+ end
51
+ end
52
+
53
+ context "testnet3" do
54
+ before do
55
+ @address = MoneyTree::Address.new network: :bitcoin_testnet
56
+ end
57
+
58
+ it "returns a testnet address" do
59
+ expect(%w(m n)).to include(@address.to_s(network: :bitcoin_testnet)[0])
60
+ end
61
+ end
62
+ end