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.
- checksums.yaml +4 -4
- data/.github/workflows/code.yml +44 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +4 -2
- data/Gemfile +13 -2
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/Rakefile +1 -1
- data/checksum/money-tree-0.11.0.gem.sha512 +1 -0
- data/checksum/money-tree-0.11.1.gem.sha512 +1 -0
- data/checksum/money-tree-0.9.0.gem.sha512 +1 -1
- data/config/openssl.conf +14 -0
- data/lib/money-tree/address.rb +14 -4
- data/lib/money-tree/key.rb +45 -31
- data/lib/money-tree/networks.rb +16 -15
- data/lib/money-tree/node.rb +65 -45
- data/lib/money-tree/support.rb +42 -17
- data/lib/money-tree/version.rb +1 -1
- data/lib/money-tree.rb +6 -6
- data/lib/openssl_extensions.rb +6 -4
- data/money-tree.gemspec +24 -18
- data/spec/{lib/money-tree → money-tree}/address_spec.rb +23 -5
- data/spec/money-tree/money_tree_spec.rb +9 -0
- data/spec/{lib/money-tree → money-tree}/node_spec.rb +552 -15
- data/spec/{lib/money-tree → money-tree}/openssl_extensions_spec.rb +4 -4
- data/spec/{lib/money-tree → money-tree}/private_key_spec.rb +10 -10
- data/spec/{lib/money-tree → money-tree}/public_key_spec.rb +51 -22
- data/spec/{lib/money-tree → money-tree}/support_spec.rb +1 -1
- data/spec/spec_helper.rb +15 -2
- metadata +39 -68
- data/certs/mattatgemco.pem +0 -24
data/lib/money-tree/support.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
-
require
|
2
|
-
require
|
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 =
|
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,
|
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
|
32
|
-
("1"*leading_zero_bytes) + int_to_base58(
|
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? ?
|
37
|
-
s =
|
38
|
-
leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 :
|
39
|
-
s = ("00"*leading_zero_bytes) + s
|
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(
|
76
|
+
digestify("SHA256", source, opts)
|
67
77
|
end
|
68
78
|
|
69
79
|
def ripemd160(source, opts = {})
|
70
|
-
digestify(
|
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,
|
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
|
data/lib/money-tree/version.rb
CHANGED
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
|
-
|
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
|
data/lib/openssl_extensions.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# encoding: ascii-8bit
|
2
2
|
|
3
|
-
require
|
3
|
+
require "openssl"
|
4
4
|
|
5
5
|
module MoneyTree
|
6
6
|
module OpenSSLExtensions
|
7
|
-
|
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(
|
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
|
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(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "money-tree/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name
|
8
|
-
spec.version
|
9
|
-
spec.authors
|
10
|
-
spec.email
|
11
|
-
spec.description
|
12
|
-
spec.summary
|
13
|
-
spec.homepage
|
14
|
-
spec.license
|
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
|
17
|
-
spec.executables
|
18
|
-
spec.test_files
|
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.
|
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.
|
24
|
-
spec.
|
25
|
-
|
26
|
-
spec.
|
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
|
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(:
|
67
|
+
subject(:wiki_v1) { MoneyTree::Address.new private_key: "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725" }
|
62
68
|
|
63
|
-
it "always regenerates the bitcoin wiki example" do
|
64
|
-
expect(
|
65
|
-
expect(
|
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
|
|