money-tree-extended 0.11.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,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