money-tree 0.11.0 → 0.11.2

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.
@@ -1,16 +1,18 @@
1
- require 'openssl'
2
- require 'base64'
1
+ require "base64"
2
+ require "bech32"
3
+ require "openssl"
3
4
 
4
5
  module MoneyTree
5
6
  module Support
6
7
  include OpenSSL
8
+ extend self
7
9
 
8
10
  INT32_MAX = 256 ** [1].pack("L*").size
9
11
  INT64_MAX = 256 ** [1].pack("Q*").size
10
12
  BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
11
13
 
12
- def int_to_base58(int_val, leading_zero_bytes=0)
13
- base58_val, base = '', BASE58_CHARS.size
14
+ def int_to_base58(int_val, leading_zero_bytes = 0)
15
+ base58_val, base = "", BASE58_CHARS.size
14
16
  while int_val > 0
15
17
  int_val, remainder = int_val.divmod(base)
16
18
  base58_val = BASE58_CHARS[remainder] + base58_val
@@ -20,25 +22,26 @@ module MoneyTree
20
22
 
21
23
  def base58_to_int(base58_val)
22
24
  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)
25
+ base58_val.reverse.each_char.with_index do |char, index|
26
+ raise ArgumentError, "Value not a valid Base58 String." unless char_index = BASE58_CHARS.index(char)
27
+ int_val += char_index * (base ** index)
26
28
  end
27
29
  int_val
28
30
  end
29
31
 
30
32
  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
+ leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : "").size / 2
34
+ ("1" * leading_zero_bytes) + int_to_base58(hex.to_i(16))
33
35
  end
34
36
 
35
37
  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
38
+ s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? "0" + s : s)
39
+ s = "" if s == "00"
40
+ leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : "").size
41
+ s = ("00" * leading_zero_bytes) + s if leading_zero_bytes > 0
40
42
  s
41
43
  end
44
+
42
45
  alias_method :base58_to_hex, :decode_base58
43
46
 
44
47
  def to_serialized_base58(hex)
@@ -49,6 +52,13 @@ module MoneyTree
49
52
  encode_base58 address
50
53
  end
51
54
 
55
+ def to_serialized_bech32(human_readable_part, hex)
56
+ segwit_addr = Bech32::SegwitAddr.new
57
+ segwit_addr.hrp = human_readable_part
58
+ segwit_addr.script_pubkey = "0000" + hex
59
+ segwit_addr.addr
60
+ end
61
+
52
62
  def from_serialized_base58(base58)
53
63
  hex = decode_base58 base58
54
64
  checksum = hex.slice!(-8..-1)
@@ -63,11 +73,11 @@ module MoneyTree
63
73
  end
64
74
 
65
75
  def sha256(source, opts = {})
66
- digestify('SHA256', source, opts)
76
+ digestify("SHA256", source, opts)
67
77
  end
68
78
 
69
79
  def ripemd160(source, opts = {})
70
- digestify('RIPEMD160', source, opts)
80
+ digestify("RIPEMD160", source, opts)
71
81
  end
72
82
 
73
83
  def encode_base64(hex)
@@ -85,7 +95,7 @@ module MoneyTree
85
95
 
86
96
  def hmac_sha512_hex(key, message)
87
97
  md = hmac_sha512(key, message)
88
- md.unpack("H*").first.rjust(64, '0')
98
+ md.unpack("H*").first.rjust(64, "0")
89
99
  end
90
100
 
91
101
  def bytes_to_int(bytes, base = 16)
@@ -95,7 +105,7 @@ module MoneyTree
95
105
  bytes.unpack("H*")[0].to_i(16)
96
106
  end
97
107
 
98
- def int_to_hex(i, size=nil)
108
+ def int_to_hex(i, size = nil)
99
109
  hex = i.to_s(16).downcase
100
110
  if (hex.size % 2) != 0
101
111
  hex = "#{0}#{hex}"
@@ -123,5 +133,20 @@ module MoneyTree
123
133
  def hex_to_int(hex)
124
134
  hex.to_i(16)
125
135
  end
136
+
137
+ def encode_p2wpkh_p2sh(value)
138
+ chk = [Digest::SHA256.hexdigest(Digest::SHA256.digest(value))].pack("H*")[0...4]
139
+ encode_base58 (value + chk).unpack("H*")[0]
140
+ end
141
+
142
+ def custom_hash_160(value)
143
+ [OpenSSL::Digest::RIPEMD160.hexdigest(Digest::SHA256.digest(value))].pack("H*")
144
+ end
145
+
146
+ def convert_p2wpkh_p2sh(key_hex, prefix)
147
+ push_20 = ["0014"].pack("H*")
148
+ script_sig = push_20 + custom_hash_160([key_hex].pack("H*"))
149
+ encode_p2wpkh_p2sh(prefix + custom_hash_160(script_sig))
150
+ end
126
151
  end
127
152
  end
@@ -1,3 +1,3 @@
1
1
  module MoneyTree
2
- VERSION = "0.11.0"
2
+ VERSION = "0.11.2"
3
3
  end
data/lib/money-tree.rb CHANGED
@@ -1,11 +1,11 @@
1
- require "openssl_extensions"
2
- require "money-tree/version"
3
1
  require "money-tree/support"
4
- require "money-tree/networks"
5
- require "money-tree/key"
2
+
6
3
  require "money-tree/address"
4
+ require "money-tree/key"
7
5
  require "money-tree/networks"
8
6
  require "money-tree/node"
7
+ require "money-tree/version"
8
+
9
+ require "openssl_extensions"
9
10
 
10
- module MoneyTree
11
- end
11
+ module MoneyTree; end
@@ -1,12 +1,14 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- require 'openssl'
3
+ require "openssl"
4
4
 
5
5
  module MoneyTree
6
6
  module OpenSSLExtensions
7
- def self.add(point_0, point_1)
7
+ extend self
8
+
9
+ def add(point_0, point_1)
8
10
  validate_points(point_0, point_1)
9
- group = OpenSSL::PKey::EC::Group.new('secp256k1')
11
+ group = OpenSSL::PKey::EC::Group.new("secp256k1")
10
12
  point_0_hex = point_0.to_bn.to_s(16)
11
13
  point_0_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_0_hex, 16))
12
14
  point_1_hex = point_1.to_bn.to_s(16)
@@ -15,7 +17,7 @@ module MoneyTree
15
17
  sum_point.to_bn.to_s(16)
16
18
  end
17
19
 
18
- def self.validate_points(*points)
20
+ def validate_points(*points)
19
21
  points.each do |point|
20
22
  if !point.is_a?(OpenSSL::PKey::EC::Point)
21
23
  raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
data/money-tree.gemspec CHANGED
@@ -1,27 +1,33 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'money-tree/version'
4
+ require "money-tree/version"
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "money-tree"
8
- spec.version = MoneyTree::VERSION
9
- spec.authors = ["Micah Winkelspecht", "Afri Schoedon"]
10
- spec.email = ["winkelspecht@gmail.com", "gems@q9f.cc"]
11
- spec.description = %q{A Ruby Gem implementation of Bitcoin HD Wallets}
12
- spec.summary = %q{Bitcoin Hierarchical Deterministic Wallets in Ruby! (Bitcoin standard BIP0032)}
13
- spec.homepage = "https://github.com/gemhq/money-tree"
14
- spec.license = "MIT"
7
+ spec.name = "money-tree"
8
+ spec.version = MoneyTree::VERSION
9
+ spec.authors = ["Micah Winkelspecht", "Afri Schoedon"]
10
+ spec.email = ["winkelspecht@gmail.com", "gems@q9f.cc"]
11
+ spec.description = %q{A Ruby Gem implementation of Bitcoin HD Wallets}
12
+ spec.summary = %q{Bitcoin Hierarchical Deterministic Wallets in Ruby! (Bitcoin standard BIP0032)}
13
+ spec.homepage = "https://github.com/GemHQ/money-tree"
14
+ spec.license = "MIT"
15
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)/})
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
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency 'openssl', '>= 2.2'
21
+ spec.metadata = {
22
+ "homepage_uri" => "https://github.com/GemHQ/money-tree",
23
+ "source_code_uri" => "https://github.com/GemHQ/money-tree",
24
+ "github_repo" => "https://github.com/GemHQ/money-tree",
25
+ "bug_tracker_uri" => "https://github.com/GemHQ/money-tree/issues",
26
+ }.freeze
22
27
 
23
- spec.add_development_dependency "bundler", "~> 2.2"
24
- spec.add_development_dependency "rake", "~> 13.0"
25
- spec.add_development_dependency "rspec", "~> 3.10"
26
- spec.add_development_dependency "pry", "~> 0.4"
28
+ spec.platform = Gem::Platform::RUBY
29
+ spec.required_ruby_version = ">= 2.7", "< 4.0"
30
+
31
+ spec.add_dependency "openssl", "~> 3.1"
32
+ spec.add_dependency "bech32", "~> 1.3"
27
33
  end
@@ -1,4 +1,4 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  describe MoneyTree::Address do
4
4
  describe "initialize" do
@@ -25,6 +25,8 @@ describe MoneyTree::Address do
25
25
  expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
26
26
  expect(address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
27
27
  expect(address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
28
+ expect(address.to_p2wpkh_p2sh).to eql("31vNN7WVDxjvc5XZVKW3qV4B3nFLxsRPnE")
29
+ expect(address.to_bech32).to eql("bc1qrlwlgt5d0sdtq882qvk3jc0sywucn76fwcmqma")
28
30
  end
29
31
 
30
32
  it "imports a private key in compressed wif format" do
@@ -32,6 +34,8 @@ describe MoneyTree::Address do
32
34
  expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
33
35
  expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
34
36
  expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
37
+ expect(address.to_p2wpkh_p2sh).to eql("31vNN7WVDxjvc5XZVKW3qV4B3nFLxsRPnE")
38
+ expect(address.to_bech32).to eql("bc1qrlwlgt5d0sdtq882qvk3jc0sywucn76fwcmqma")
35
39
  end
36
40
 
37
41
  it "imports a private key in uncompressed wif format" do
@@ -49,6 +53,8 @@ describe MoneyTree::Address do
49
53
  it "returns compressed base58 public key" do
50
54
  expect(@address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
51
55
  expect(@address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
56
+ expect(@address.to_p2wpkh_p2sh).to eql("31vNN7WVDxjvc5XZVKW3qV4B3nFLxsRPnE")
57
+ expect(@address.to_bech32).to eql("bc1qrlwlgt5d0sdtq882qvk3jc0sywucn76fwcmqma")
52
58
  end
53
59
 
54
60
  it "returns compressed WIF private key" do
@@ -58,11 +64,23 @@ describe MoneyTree::Address do
58
64
 
59
65
  context "bitcoin wiki" do
60
66
  # ref https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
61
- subject(:wiki) { MoneyTree::Address.new private_key: '18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725' }
67
+ subject(:wiki_v1) { MoneyTree::Address.new private_key: "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725" }
62
68
 
63
- it "always regenerates the bitcoin wiki example" do
64
- expect(wiki.public_key.key).to eq "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
65
- expect(wiki.to_s).to eq "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"
69
+ it "always regenerates the bitcoin wiki v1 example" do
70
+ expect(wiki_v1.public_key.key).to eq "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
71
+ expect(wiki_v1.to_s).to eq "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"
72
+ expect(wiki_v1.to_p2wpkh_p2sh).to eql("3BxwGNjvG4CP14tAZodgYyZ7UTjruYDyAM")
73
+ expect(wiki_v1.to_bech32).to eql("bc1q7499s50fxu4c0qg23esvm5h8elvqkm33r2tdza")
74
+ end
75
+
76
+ # ref https://en.bitcoin.it/wiki/Bech32
77
+ subject(:wiki_bech32) { MoneyTree::Address.new private_key: "0000000000000000000000000000000000000000000000000000000000000001" }
78
+
79
+ it "always regenerates the bitcoin wiki v1 example" do
80
+ expect(wiki_bech32.public_key.key).to eq "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"
81
+ expect(wiki_bech32.to_s).to eq "1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH"
82
+ expect(wiki_bech32.to_p2wpkh_p2sh).to eql("3JvL6Ymt8MVWiCNHC7oWU6nLeHNJKLZGLN")
83
+ expect(wiki_bech32.to_bech32).to eql("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4")
66
84
  end
67
85
  end
68
86
 
@@ -0,0 +1,9 @@
1
+ require "spec_helper"
2
+
3
+ describe MoneyTree do
4
+ it "0.11.2 works" do
5
+
6
+ # placeholder to set up spec in future
7
+ expect(MoneyTree::VERSION).to eq "0.11.2"
8
+ end
9
+ end