money-tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4fb106149c53cf468220d50f609604835f0771d0
4
+ data.tar.gz: ae65887642b07d8bcfdefae06452d4b0a91d0f2a
5
+ SHA512:
6
+ metadata.gz: ea72d026539e19514e95d51a0e2404dfd594b6d524b4debfe491b762d0d1d24f1cc0d3ef0cbd1fb20db29de0403c7d7066d3190f4aca3736853bfd4d6354853a
7
+ data.tar.gz: c2e21de52d10e2f9d2917be4a6db7cd5e9c8b6588959963ead0648a6b219b479a1c5f67ac45299f0a31ff380562c6a138a449df656988a0ad1310a77f0762860
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in money-tree.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Micah Winkelspecht
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # MoneyTree
2
+
3
+ MoneyTree is a Ruby implementation of Bitcoin Wallets. Specifically, it supports [Hierachical Deterministic wallets](http://) according to the protocol specified in [BIP0032](https://en.bitcoin.it/wiki/BIP_0032).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'money-tree'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install money-tree
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/money-tree.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "money-tree/version"
2
+ require "money-tree/support"
3
+ require "money-tree/networks"
4
+ require "money-tree/key"
5
+ require "money-tree/address"
6
+ require "money-tree/networks"
7
+ require "money-tree/node"
8
+
9
+ module MoneyTree
10
+
11
+ end
@@ -0,0 +1,15 @@
1
+ module MoneyTree
2
+ class Address
3
+ attr_accessor :private_key, :public_key
4
+
5
+ def initialize(opts = {})
6
+ @private_key = MoneyTree::PrivateKey.new key: opts[:private_key]
7
+ @public_key = MoneyTree::PublicKey.new(@private_key)
8
+ end
9
+
10
+ def to_s
11
+ public_key.to_s
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,200 @@
1
+ # encoding ascii-8bit
2
+
3
+ require 'openssl'
4
+ require 'pry'
5
+
6
+ module MoneyTree
7
+ class Key
8
+ include OpenSSL
9
+ include Support
10
+ class KeyInvalid < Exception; end
11
+ class KeyGenerationFailure < Exception; end
12
+ class KeyImportFailure < Exception; end
13
+ class KeyFormatNotFound < Exception; end
14
+ class InvalidWIFFormat < Exception; end
15
+
16
+ attr_reader :options, :key
17
+ attr_accessor :ec_key
18
+
19
+ GROUP_NAME = 'secp256k1'
20
+ GROUP_UNCOMPRESSED = "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".to_i(16)
21
+ GROUP_COMPRESSED = "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798".to_i(16)
22
+ ORDER = "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141".to_i(16)
23
+
24
+ def valid?(eckey = nil)
25
+ eckey ||= ec_key
26
+ eckey.nil? ? false : eckey.check_key
27
+ end
28
+
29
+ def to_bytes
30
+ hex_to_bytes to_hex
31
+ end
32
+
33
+ def to_i
34
+ bytes_to_int to_bytes
35
+ end
36
+ end
37
+
38
+ class PrivateKey < Key
39
+
40
+ attr_reader :raw_key
41
+
42
+ def initialize(opts = {})
43
+ @options = opts
44
+ # @ec_key = EC_KEY_new_by_curve_name(NID_secp256k1)
45
+ @ec_key = PKey::EC.new 'secp256k1'
46
+ if @options[:key]
47
+ @raw_key = @options[:key]
48
+ @key = parse_raw_key
49
+ import
50
+ else
51
+ generate
52
+ @key = to_hex
53
+ end
54
+ end
55
+
56
+ def generate
57
+ ec_key.generate_key
58
+ end
59
+
60
+ def import
61
+ ec_key.private_key = BN.new(key, 16)
62
+ set_public_key
63
+ end
64
+
65
+ def calculate_public_key(opts = {})
66
+ opts[:compressed] = true unless opts[:compressed] == false
67
+ group = ec_key.group
68
+ group.point_conversion_form = opts[:compressed] ? :compressed : :uncompressed
69
+ point = group.generator.mul ec_key.private_key
70
+ end
71
+
72
+ def set_public_key(opts = {})
73
+ ec_key.public_key = calculate_public_key(opts)
74
+ end
75
+
76
+ def parse_raw_key
77
+ result = if raw_key.is_a?(Bignum) then int_to_hex(raw_key)
78
+ elsif hex_format? then raw_key
79
+ elsif compressed_wif_format? then from_wif
80
+ elsif uncompressed_wif_format? then from_wif
81
+ elsif base64_format? then from_base64
82
+ else
83
+ raise raw_key.inspect
84
+ raise KeyFormatNotFound
85
+ end
86
+ result.downcase
87
+ end
88
+
89
+ def from_wif(wif = raw_key)
90
+ compressed = wif.length == 52
91
+ validate_wif(wif)
92
+ hex = decode_base58(wif)
93
+ last_char = compressed ? -11 : -9
94
+ hex.slice(2..last_char)
95
+ end
96
+
97
+ def from_base64
98
+ decode_base64(raw_key)
99
+ end
100
+
101
+ def compressed_wif_format?
102
+ raw_key.length == 52 && MoneyTree::NETWORKS[:bitcoin][:compressed_wif_chars].include?(raw_key.slice(0))
103
+ end
104
+
105
+ def uncompressed_wif_format?
106
+ raw_key.length == 51 && raw_key.slice(0) == MoneyTree::NETWORKS[:bitcoin][:uncompressed_wif_char]
107
+ end
108
+
109
+ def base64_format?
110
+ raw_key.length == 44
111
+ end
112
+
113
+ def hex_format?
114
+ raw_key.length == 64 && !raw_key[/\H/]
115
+ end
116
+
117
+ def to_hex
118
+ int_to_hex @ec_key.private_key
119
+ end
120
+
121
+ def to_wif(opts = {})
122
+ opts[:compressed] = true unless opts[:compressed] == false
123
+ source = MoneyTree::NETWORKS[:bitcoin][:privkey_version] + to_hex
124
+ source += MoneyTree::NETWORKS[:bitcoin][:privkey_compression_flag] if opts[:compressed]
125
+ hash = sha256(source)
126
+ hash = sha256(hash)
127
+ checksum = hash.slice(0..7)
128
+ source_with_checksum = source + checksum
129
+ encode_base58(source_with_checksum)
130
+ end
131
+
132
+ def wif_valid?(wif)
133
+ hex = decode_base58(wif)
134
+ return false unless hex.slice(0..1) == MoneyTree::NETWORKS[:bitcoin][:privkey_version]
135
+ checksum = hex.chars.to_a.pop(8).join
136
+ source = hex.slice(0..-9)
137
+ hash = sha256(source)
138
+ hash = sha256(hash)
139
+ hash_checksum = hash.slice(0..7)
140
+ checksum == hash_checksum
141
+ end
142
+
143
+ def validate_wif(wif)
144
+ raise InvalidWIFFormat unless wif_valid?(wif)
145
+ end
146
+
147
+ def to_base64
148
+ encode_base64(to_hex)
149
+ end
150
+
151
+ def to_s
152
+ to_wif
153
+ end
154
+
155
+ end
156
+
157
+ class PublicKey < Key
158
+ attr_reader :private_key
159
+
160
+ def initialize(p_key, opts = {})
161
+ raise "Must initialize with a MoneyTree::PrivateKey" unless p_key.is_a?(PrivateKey)
162
+ @private_key = p_key
163
+ @ec_key = @private_key.ec_key
164
+ @options = opts
165
+ @key = @options[:key] || to_hex
166
+ end
167
+
168
+ def to_hex(opts = {})
169
+ int_to_hex to_i(opts)
170
+ end
171
+
172
+ def to_i(opts = {})
173
+ private_key.calculate_public_key(opts).to_bn.to_i
174
+ end
175
+
176
+ def to_ripemd160(opts = {})
177
+ hash = sha256 to_hex(opts)
178
+ ripemd160 hash
179
+ end
180
+
181
+ def to_address(opts = {})
182
+ hash = to_ripemd160(opts)
183
+ address = MoneyTree::NETWORKS[:bitcoin][:address_version] + hash
184
+ to_serialized_base58 address
185
+ end
186
+
187
+ def to_fingerprint(opts = {})
188
+ hash = to_ripemd160(opts)
189
+ hash.slice(0..7)
190
+ end
191
+
192
+ def to_s(opts = {})
193
+ to_address(opts)
194
+ end
195
+
196
+ def to_bytes(opts = {})
197
+ int_to_bytes to_i(opts)
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,15 @@
1
+ module MoneyTree
2
+ NETWORKS = {
3
+ bitcoin: {
4
+ address_version: '00',
5
+ p2sh_version: '05',
6
+ privkey_version: '80',
7
+ privkey_compression_flag: '01',
8
+ extended_privkey_version: "0488ade4",
9
+ extended_pubkey_version: "0488b21e",
10
+ compressed_wif_chars: %w(K L),
11
+ uncompressed_wif_char: '5',
12
+ protocol_version: 70001
13
+ }
14
+ }
15
+ end
@@ -0,0 +1,232 @@
1
+ module MoneyTree
2
+ class Node
3
+ include Support
4
+ attr_reader :private_key, :public_key, :chain_code, :is_private, :depth, :index, :parent
5
+
6
+ class PublicDerivationFailure < Exception; end
7
+ class InvalidKeyForIndex < Exception; end
8
+
9
+ def initialize(opts = {})
10
+ @depth = opts[:depth]
11
+ @index = opts[:index]
12
+ @is_private = opts[:is_private]
13
+ @private_key = opts[:private_key]
14
+ @public_key = opts[:public_key]
15
+ @chain_code = opts[:chain_code]
16
+ @parent = opts[:parent]
17
+ end
18
+
19
+ def is_private?
20
+ is_private == true
21
+ end
22
+
23
+ def index_hex(i = index)
24
+ i.to_s(16).rjust(8, "0")
25
+ end
26
+
27
+ def depth_hex(depth)
28
+ depth.to_s(16).rjust(2, "0")
29
+ end
30
+
31
+ def private_derivation_private_key_message(i)
32
+ "\x00" + private_key.to_bytes + i_as_bytes(i)
33
+ end
34
+
35
+ def public_derivation_private_key_message(i)
36
+ public_key.to_bytes << i_as_bytes(i)
37
+ end
38
+
39
+ # TODO: Complete public key derivation message
40
+ # def public_derivation_public_key_message(i)
41
+ # public_key.to_bytes + i_as_bytes(i)
42
+ # end
43
+
44
+ def i_as_bytes(i)
45
+ [i].pack('N')
46
+ end
47
+
48
+ def derive_private_key(i = 0)
49
+ message = i >= 0x80000000 ? private_derivation_private_key_message(i) : public_derivation_private_key_message(i)
50
+ hash = hmac_sha512 int_to_bytes(chain_code), message
51
+ left_int = left_from_hash(hash)
52
+ raise InvalidKeyForIndex if left_int >= MoneyTree::Key::ORDER # very low probability
53
+ child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
54
+ raise InvalidKeyForIndex if child_private_key == 0 # very low probability
55
+ child_chain_code = right_from_hash(hash)
56
+ return child_private_key, child_chain_code
57
+ end
58
+
59
+ def left_from_hash(hash)
60
+ bytes_to_int hash.bytes.to_a[0..31]
61
+ end
62
+
63
+ def right_from_hash(hash)
64
+ bytes_to_int hash.bytes.to_a[32..-1]
65
+ end
66
+
67
+ # TODO: Complete public key derivation
68
+ # def derive_public_key(i = 0, opts = {})
69
+ # raise PublicDerivationFailure unless i < 0x80000000
70
+ # hash = hmac_sha512([chain_code].pack("H*"), public_key.to_hex + index_hex(i))
71
+ # temp_key = MoneyTree::PrivateKey.new key: hash[0..63]
72
+ # temp_pub_key = MoneyTree::PublicKey.new temp_key
73
+ # child_public_key = (temp_key.to_hex.to_i(16) + temp_pub_key.to_hex.to_i(16))
74
+ # child_chain_code = hash[64..-1]
75
+ # return child_public_key, child_chain_code
76
+ # end
77
+
78
+ def to_serialized_hex(type = :public)
79
+ version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
80
+ hex = MoneyTree::NETWORKS[:bitcoin][version_key] # version (4 bytes)
81
+ hex += depth_hex(depth) # depth (1 byte)
82
+ hex += depth.zero? ? '00000000' : parent.to_fingerprint# fingerprint of key (4 bytes)
83
+ hex += index_hex(index) # child number i (4 bytes)
84
+ hex += chain_code_hex
85
+ hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.to_hex
86
+ end
87
+
88
+ def to_serialized_address(type = :public)
89
+ to_serialized_base58 to_serialized_hex(type)
90
+ end
91
+
92
+ def to_identifier
93
+ public_key.to_ripemd160
94
+ end
95
+
96
+ def to_fingerprint
97
+ to_identifier[0..7]
98
+ end
99
+
100
+ def to_address
101
+ address = MoneyTree::NETWORKS[:bitcoin][:address_version] + to_identifier
102
+ to_serialized_base58 address
103
+ end
104
+
105
+ def subnode(i = 0, opts = {})
106
+ # opts[:as_private] = is_private? unless opts[:as_private] == false
107
+ child_private_key, child_chain_code = derive_private_key(i)
108
+ child_private_key = MoneyTree::PrivateKey.new key: child_private_key
109
+ child_public_key = MoneyTree::PublicKey.new child_private_key
110
+
111
+ MoneyTree::Node.new depth: depth+1,
112
+ index: i,
113
+ is_private: i >= 0x80000000,
114
+ private_key: child_private_key,
115
+ public_key: child_public_key,
116
+ chain_code: child_chain_code,
117
+ parent: self
118
+ end
119
+
120
+ # path: a path of subkeys denoted by numbers and slashes. Use
121
+ # p or i<0 for private key derivation. End with .pub to force
122
+ # the key public.
123
+ #
124
+ # Examples:
125
+ # 1p/-5/2/1 would call subkey(i=1, is_prime=True).subkey(i=-5).
126
+ # subkey(i=2).subkey(i=1) and then yield the private key
127
+ # 0/0/458.pub would call subkey(i=0).subkey(i=0).subkey(i=458) and
128
+ # then yield the public key
129
+ #
130
+ # You should choose either the p or the negative number convention for private key derivation.
131
+ def node_for_path(path)
132
+ force_public = path[-4..-1] == '.pub'
133
+ path = path[0..-4] if force_public
134
+ parts = path.split('/')
135
+ nodes = []
136
+ parts.each_with_index do |part, depth|
137
+ if part =~ /m/i
138
+ nodes << self
139
+ else
140
+ i = parse_index(part)
141
+ nodes << nodes.last.subnode(i)
142
+ end
143
+ end
144
+ nodes.last
145
+ end
146
+
147
+ def parse_index(path_part)
148
+ is_prime = %w(p ').include? path_part[-1]
149
+ i = path_part.to_i
150
+
151
+ i = if i < 0
152
+ i
153
+ elsif is_prime
154
+ i | 0x80000000
155
+ else
156
+ i & 0x7fffffff
157
+ end
158
+ end
159
+
160
+ def chain_code_hex
161
+ int_to_hex chain_code
162
+ end
163
+ end
164
+
165
+ class Master < Node
166
+ module SeedGeneration
167
+ class Failure < Exception; end
168
+ class RNGFailure < Failure; end
169
+ class LengthFailure < Failure; end
170
+ class ValidityError < Failure; end
171
+ class ImportError < Failure; end
172
+ class TooManyAttempts < Failure; end
173
+ end
174
+
175
+ HD_WALLET_BASE_KEY = "Bitcoin seed"
176
+ RANDOM_SEED_SIZE = 32
177
+
178
+ attr_reader :seed, :seed_hash
179
+
180
+ def initialize(opts = {})
181
+ @depth = 0
182
+ @index = 0
183
+ @is_private = true
184
+ @seed_generation_attempt = 0
185
+ opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
186
+ if opts[:seed]
187
+ @seed = opts[:seed]
188
+ @seed_hash = generate_seed_hash(@seed)
189
+ raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
190
+ else
191
+ generate_seed_until_valid
192
+ end
193
+ set_master_keys
194
+ end
195
+
196
+ def generate_seed_until_valid
197
+ begin
198
+ @seed = generate_seed
199
+ @seed_hash = generate_seed_hash(@seed)
200
+ raise SeedGeneration::ValidityError unless seed_valid?(@seed_hash)
201
+ @seed_generation_attempt = 0
202
+ rescue SeedGeneration::Failure
203
+ @seed_generation_attempt += 1
204
+ @seed_generation_attempt < 10 ? generate_seed_until_valid : raise(SeedGeneration::TooManyAttempts)
205
+ end
206
+ end
207
+
208
+ def generate_seed
209
+ OpenSSL::Random.random_bytes(32)
210
+ end
211
+
212
+ def generate_seed_hash(seed)
213
+ hmac_sha512 HD_WALLET_BASE_KEY, seed
214
+ end
215
+
216
+ def seed_valid?(seed_hash)
217
+ return false unless seed_hash.bytesize == 64
218
+ master_key = left_from_hash(seed_hash)
219
+ !master_key.zero? && master_key < MoneyTree::Key::ORDER
220
+ end
221
+
222
+ def is_hex?(str)
223
+ str =~ /^[0-9A-F]+$/i && str.length % 2 == 0
224
+ end
225
+
226
+ def set_master_keys
227
+ @private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
228
+ @chain_code = right_from_hash(seed_hash)
229
+ @public_key = MoneyTree::PublicKey.new @private_key
230
+ end
231
+ end
232
+ end