money-tree 0.11.0 → 0.11.2

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